/************************************************************************
* cucipop - The Cubic Circle POP3 daemon implementation *
* *
* A fully compliant RFC1939 POP3 daemon implementation. *
* Features: light load, fast query path, NFS resistant, *
* in-situ mailbox rewrite, no tempfiles, short locking, *
* standard BSD mailbox format, SysV Content-Length extension *
* *
* Copyright (c) 1996-1998, S.R. van den Berg, The Netherlands *
* #include "README" *
************************************************************************/
#ifdef RCS
static /*const*/char rcsid[]=
"$Id: cucipop.c,v 1.31 1998/05/13 16:57:39 srb Exp $";
#endif
#include "patchlevel.h"
#include "config.h"
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/utsname.h>
#include <sys/stat.h>
#include <sysexits.h>
#include <fcntl.h>
#include <string.h>
#include <pwd.h>
#include <syslog.h>
#include <ctype.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/time.h>
#include <time.h>
#ifdef INET6
#include <netdb.h>
#endif
#include "cucipop.h"
#include "atotime.h"
#include "authenticate.h"
#include "locking.h"
#include "dbops.h"
#include "hsort.h"
#ifdef APOP
#include <md5.h>
#endif
#define MD5_DIGLEN 16
#define BIN2HEXLEN (MD5_DIGLEN*2)
#define Signal(sig,fn) signal(sig,(void(*)())(fn))
#define Vuser (auth_logname?auth_logname:user)
#define Bintohex(arg,checkendian) (sizeof(arg)>MD5_DIGLEN?\
(fail(#arg" too large"),terminate(EX_SOFTWARE),""):\
bintohex(&(arg),sizeof(arg),checkendian))
static const char*const nullp,From_[]=FROM,XFrom_[]=XFROM,cucipopn[]="cucipop",
Received[]=
"Received: by %s (mbox %s)\r\n (with Cubic Circle's %s (%s) %.24s)\r\n",
version[]=VERSION,bulletins_path[]=BULLETINS_PATH;
static char linebuf[LINEBUFLEN+1],*arg1,*arg2,user[ARGLEN+1],*host,*timestamp;
static FILE*fmbox,*sockout;
static size_t hostnduser;
FILE*sockin;
long spoofcheck;
#ifdef INET6
static struct sockaddr_storage peername;
#else
static struct sockaddr_in peername;
#endif
static int namelen;
struct msg
{ size_t order;
off_t start;
int file;
time_t mtime;
#ifdef UIDL
uidl_t uidl;
unsigned statskip;
#endif
long virtsize,realsize;
unsigned char deleted,tooold;
}*msgs;
#define MSGS(i) (msgs[msgs[i].order])
static size_t msgs_filled,priv_msgs_filled,msgs_max,totmsgs,transfmsgs;
static long totsize,transfsize;
static off_t oldend;
static unsigned audit,berkeleyformat;
static time_t tsabotage,tsessionstart;
static const char*mailbox;
static struct stateinfo si;
#define TBUL_READ(x) (si.readbulletin[(x)/8]&1<<((x)&7))
#define SBUL_READ(x) (si.readbulletin[(x)/8]|=1<<((x)&7))
#define SBUL_UNREAD(x) (si.readbulletin[(x)/8]&=~(1<<((x)&7)))
static void bailout(void);
static void zombiecollect(void)
{ while(waitpid((pid_t)-1,(int*)0,WNOHANG)>0); /* collect any zombies */
}
void blocksignals(void)
{ signal(SIGHUP,SIG_IGN);signal(SIGINT,SIG_IGN);signal(SIGTERM,SIG_IGN);
signal(SIGPIPE,SIG_IGN);signal(SIGALRM,SIG_IGN);
#ifdef SIGURG
signal(SIGURG,SIG_IGN);
#endif
}
void setsignals(void)
{ Signal(SIGHUP,bailout);Signal(SIGINT,bailout);Signal(SIGTERM,bailout);
Signal(SIGPIPE,bailout);Signal(SIGALRM,bailout);
#ifdef SIGURG
Signal(SIGURG,bailout);
#endif
}
static size_t findmsg(void)
{ size_t i;
return arg1&&(i=strtol(arg1,(char**)0,10)-1)<msgs_filled&&
!(MSGS(i).deleted&1)?i+1:0;
}
static const char*bintohex(const void*ptr,size_t size,const int checkendian)
{ static char hex[BIN2HEXLEN+1],*hp;int inc=1;
{ const int test=1;
if(checkendian&&!*(const char*)&test)
ptr=(const char*)ptr+size+(inc=-1);
}
for(hp=hex;size--;ptr=(const char*)ptr+inc,hp+=2)
sprintf(hp,"%02x",*(unsigned char*)ptr);
return hex;
}
#ifdef INET6
static const char *peer(void)
{
static char hbuf[NI_MAXHOST];
#ifdef NI_WITHSCOPEID
const int niflags = NI_NUMERICHOST | NI_WITHSCOPEID;
#else
const int niflags = NI_NUMERICHOST;
#endif
if (getnameinfo((struct sockaddr *)&peername, namelen, hbuf, sizeof(hbuf),
NULL, 0, niflags)) {
strncpy(hbuf, "invalid", sizeof(hbuf) - 1);
hbuf[sizeof(hbuf) - 1] = '\0';
}
return hbuf;
}
#else
static const char*iptoascii(const struct sockaddr_in*addr)
{ static char ip[4*4];unsigned char*p=(unsigned char*)&addr->sin_addr.s_addr;
sprintf(ip,"%d.%d.%d.%d",p[0],p[1],p[2],p[3]);
return ip;
}
static const char*peer(void)
{ return iptoascii(&peername);
}
#endif
void logfinal(int level,int lost,unsigned autodel)
{ if(!*user||transfsize||totmsgs||audit)
syslog(level,"%s%s %s %ld, %s%ld (%ld), %ld (%ld)",lost?"lost ":"",Vuser,
peer(),(long)(time((time_t*)0)-tsessionstart),autodel?"-":"",
(long)transfmsgs,transfsize,(long)totmsgs,totsize);
}
static void terminate(int code)
{ unlockmbox();
if(transfmsgs)
{ struct stateinfo*sip;
if(sip=getstate(Vuser,tsessionstart))
si=*sip;
si.lastabort=tsessionstart;
if(!si.lastsuccess)
si.lastsuccess=tsessionstart;
if(si.brokenstate+1>si.brokenstate)
si.brokenstate++;
putstate(Vuser,&si);
}
closedb();exitappdb();logfinal(LOG_NOTICE,1,0);exit(code);
}
static void bailout(void)
{ const static char abnormal[]="-ERR Abnormal shutdown\r\n";
blocksignals();write(1,abnormal,STRLEN(abnormal));terminate(EX_NOINPUT);
}
void castlower(char*p)
{ for(;*p=tolower(*p);p++);
}
static int cmpbuf(const char*p)
{ return!strcmp(p,linebuf);
}
static int readcommand(unsigned autodel)
{ fflush(sockout);alarm(TIMEOUT);
if(!fgets(linebuf,LINEBUFLEN+1,sockin))
{ if(autodel)
return 0;
terminate(EX_PROTOCOL);
}
alarm(TIMEOUT);arg2=0;
if((arg1=strchr(linebuf,'\r'))||(arg1=strchr(linebuf,'\n')))
*arg1='\0';
if(arg1=strchr(linebuf,' '))
{ *arg1++='\0';
if(arg2=strchr(arg1,' '))
{ *arg2++='\0';
if(strlen(arg2)>ARGLEN)
arg2[ARGLEN]='\0';
} /* some silly popclient called E-mail connection */
else if(!*arg1) /* adds spurious spaces */
{ arg1=0; /* at the end of the command line */
goto noarg1;
}
if(strlen(arg1)>ARGLEN)
arg1[ARGLEN]='\0';
}
noarg1:
castlower(linebuf);
return 1;
}
static void fail(const char*msg)
{ fprintf(sockout,"-ERR %s\r\n",msg);
}
static void loginvalcmd(void)
{ static const char noarg[]="-";
syslog(LOG_DEBUG,"Invalid command %s %s %s",linebuf,arg1?arg1:noarg,
arg2?arg2:noarg);
}
static void invalcmd(const char*cmds)
{ loginvalcmd();
fprintf(sockout,"-ERR Invalid command, try one of: %s\r\n",cmds);
}
static void outofmem(void)
{ syslog(LOG_ALERT,"out of memory");
if(sockout)
fail("Out of memory");
terminate(EX_OSERR);
}
void wipestring(char*string)
{ while(*string)
*string++='\0';
}
static const auth_identity*transmogrify(const auth_identity*pass)
{ initappdb();opendb();
return setuid(auth_whatuid(pass))?(auth_identity*)0:pass;
}
static const auth_identity*checkpw(const char*pw)
{ const auth_identity*pass;int valid=0;
if(!(pass=auth_finduser(user,fileno(sockin)))||auth_whatuid(pass)<LOWEST_UID)
goto ret0;
;{ char*secret;
if((secret=(char*)auth_getsecret(pass))&&*secret)
valid=!strcmp(pw,secret),wipestring(secret);
}
if(!valid&&!auth_checkpassword(pass,pw,0))
ret0:
return 0;
return transmogrify(pass);
}
static const auth_identity*checkchallenge(const char*ch)
{ const auth_identity*pass;
#ifdef APOP
if(pass=auth_finduser(user,fileno(sockin)))
{ size_t seclen,stlen;
;{ char*secret;
if(!(secret=(char*)auth_getsecret(pass))||!*secret)
goto ret0;
if(!(timestamp=realloc(timestamp,
(stlen=strlen(timestamp))+(seclen=strlen(secret))+1)))
outofmem();
strcpy(timestamp+stlen,secret);wipestring(secret);
}
;{ MD5_CTX context;unsigned char digest[MD5_DIGLEN];
MD5Init(&context);
MD5Update(&context,(unsigned char*)timestamp,stlen+seclen);
wipestring(timestamp+stlen);MD5Final(digest,&context);
if(!strcmp(Bintohex(digest,0),ch))
return transmogrify(pass);
}
}
ret0:
#endif
return 0;
}
static void corrupted(off_t offset)
{ syslog(LOG_CRIT,"mailbox %s corrupted at %ld",user,(long)offset);
fail("Mailbox corrupted");terminate(EX_DATAERR);
}
static void addblock(const off_t start)
{ if(msgs_filled==msgs_max&&
!(msgs=realloc(msgs,((msgs_max+=GROWSTEP)+1)*sizeof*msgs)))
outofmem();
msgs[msgs_filled].order=msgs_filled;msgs[msgs_filled].start=start;
msgs[msgs_filled].virtsize=0;msgs[msgs_filled++].deleted=0;
}
char*bullname(int bullno)
{ static char buf[4];
#if MAXBULLETINS>1000
#error Increase the buf array length if you want to do this
#endif
sprintf(buf,"%02d",bullno);
return buf;
}
FILE*openbulletin(const int extrafile,FILE*fp)
{ if(extrafile>=0)
fp=fopen(bullname(extrafile),"rb");
return fp;
}
static void scanfile(const int extrafile,FILE*fp,const time_t agecutoff)
{ size_t virtsize,extra,omf;int fdsockin;off_t start,end;uidl_t uidl;long clen;
unsigned inheader,statskip;
clen=end=start=statskip=0;fdsockin=-1;omf=msgs_filled;
virtsize=extra=STRLEN(XFrom_)-STRLEN(From_)+STRLEN(Received)+hostnduser-2*2+
STRLEN(cucipopn)-2+STRLEN(version)-2+24-5;
if(!(fp=openbulletin(extrafile,fp)))
if(extrafile<0)
goto nomailbox;
else
return;
if(extrafile<0)
end=oldend,fdsockin=fileno(sockin);
else
{ struct stat stbuf;
fstat(fileno(fp),&stbuf);end=stbuf.st_size;
}
fcntl(fdsockin,F_SETFL,(long)O_NONBLOCK); /* non-blocking spoof enable */
for(;;)
{ unsigned newlines=1;
if(clen<=0)
{ const char*p;int c;
for(p=From_;;)
{ if(!end)
goto nomailbox;
end--;
if(*p!=(c=getc(fp)))
{ ungetc(c,fp);end++;
break;
}
newlines=2;
if(!*++p)
{ struct msg*msp;
inheader=1;addblock(start);(msp=msgs+msgs_filled-1)->deleted=0;
msp->tooold=1;msp->file=extrafile;msp->mtime=agecutoff;
if(msgs_filled-omf>1)
{ msp--;
totsize+=msp->virtsize=virtsize+
(msp->realsize=start-msp->start);
#ifdef UIDL
msp->uidl=uidl;msp->statskip=statskip;
#endif
}
statskip=0;uidl=tsabotage;virtsize=extra;
#define DATELEN 20
if(end>DATELEN+4)
{ char circ[DATELEN],circo[DATELEN+1];unsigned rot,frot;
for(circo[DATELEN]=frot=rot=0;;rot=(rot+1)%DATELEN)
{ if(!end)
goto nomailbox;
end--;virtsize++;start++;
if((c=getc(fp))=='\n')
break;
circ[rot]=c;frot++;
}
if(frot>=DATELEN)
{ for(frot=0;frot<DATELEN;frot++,rot=(rot+1)%DATELEN)
circo[frot]=circ[rot];
if(extrafile<0)
{ time_t mtime;struct msg*msp=msgs+msgs_filled-1;
if((mtime=atotime(circo))-agecutoff>=0)
{ if(tsabotage)
uidl=0;
msp->tooold=0;
}
msp->mtime=mtime;
}
}
}
break;
}
}
start+=p-From_;
}
nomailbox:
;{ off_t oend;register int c;
oend=end;
do
{ if(!end--)
goto thatsall;
if(!inheader)
clen--;
c=getc(fp);
if(spoofcheck>=0&&!(++spoofcheck&SPOOFMASK))
{ char c;
spoofcheck=0;
switch(read(fdsockin,&c,1))
{ case 1:spoofcheck=-1;ungetc(c,sockin);
break;
case 0:
goto thatsall;
}
}
trynl:
#ifdef UIDL
uidl=uidl*67067L+(unsigned char)c;
#endif
if(c=='\n')
isnl: { virtsize++;
if(--newlines&&inheader)
{ static const char contlength[]=CONTLENGTH,
statfield[]=STATUSFIELD,xstatfield[]=XSTATUSFIELD;
const char*p,*ps,*psx;unsigned skiplen;uidl_t luidl;
p=berkeleyformat?0:contlength;ps=statfield;psx=xstatfield;
for(skiplen=0,luidl=uidl;;)
{ if(!end)
goto thatsall;
end--;c=getc(fp);
if(p&&*p!=tolower(c))
p=0;
if(ps&&*ps!=tolower(c))
ps=0;
if(psx&&*psx!=tolower(c))
psx=0;
if(!p&&!ps&&!psx)
{ uidl=luidl;
goto trynl;
}
#ifdef UIDL
luidl=luidl*67067L+(unsigned char)c;
#endif
newlines=2;skiplen++;
if(ps&&!*++ps||psx&&!*++psx)
{ statskip+=skiplen+1;
do
{ if(!end)
goto thatsall;
end--;statskip++;
}
while(fgetc(fp)!='\n');
goto isnl;
}
if(p&&!*++p)
{ for(;;)
{ if(!end)
goto thatsall;
end--;
switch(c=fgetc(fp))
{ case ' ':case '\t':continue;
}
break;
}
for(clen=0;;end--,c=fgetc(fp))
{ if(c<'0'||c>'9')
goto trynl;
clen=((unsigned long)clen*10)+c-'0';
if(!end) /* memory requirements to a perfect fit */
thatsall: { struct msg*msp;
fcntl(fdsockin,F_SETFL,0L); /* block again */
if(clen>0)
syslog(LOG_WARNING,"%s mailbox %ld short",
mailbox,(long)clen);
msp=msgs+msgs_filled;start+=oend;msp->deleted=1;
if(msgs_filled>omf)
{ msp--;
totsize+=msp->virtsize=virtsize+
(msp->realsize=start-msp->start);
#ifdef UIDL
msp->uidl=uidl;msp->statskip=statskip;
#endif
}
if(extrafile>=0)
fclose(fp);
return;
}
}
}
}
}
if(!newlines&&!inheader&&clen>0)
newlines=1;
}
else
newlines=2;
}
while(newlines);
inheader=0;start+=oend-end;
}
}
}
static int mcmp(const struct msg*a,const struct msg*b)
{ a=msgs+a->order;b=msgs+b->order;
switch(si.brokenstate)
{ case 0:
return -(a->order<b->order);
case 1:
return -(a->realsize<b->realsize);
default:
if(tsessionstart-a->mtime<DIRECTFETCHAGE||
tsessionstart-b->mtime<DIRECTFETCHAGE)
return -(a->mtime>b->mtime);
case 2:
{ unsigned sa=a->realsize/BSIZECATEGORY,sb=b->realsize/BSIZECATEGORY;
return sa<sb?-1:sa>sb?1:-(a->mtime>b->mtime);
}
}
}
static void mswap(struct msg*a,struct msg*b)
{ int t;
t=a->order;a->order=b->order;b->order=t;
}
static int openmailbox(const auth_identity*pass,
const time_t agecutoff)
{ FILE*fp;
alarm(0);
if(!(fmbox=fp=fopen(mailbox=auth_mailboxname((auth_identity*)pass),"r+b")))
{ if(errno==ENOENT)
goto nomailbox; /* no mailbox found */
return 0;
}
ungetc(fgetc(fp),fp); /* prime atime */
;{ struct stat stbuf;int fd;
fstat(fd=fileno(fp),&stbuf);
if(stbuf.st_atime-stbuf.st_mtime<32) /* modification in progress? */
{ if(!lockmbox(fd,mailbox))
return 0;
fstat(fd,&stbuf);unlockmbox();setsignals();
}
oldend=stbuf.st_size;
}
nomailbox:
;{ struct stateinfo*sip;
if(sip=getstate(Vuser,tsessionstart))
{ unsigned i;
si=*sip;
for(i=priv_msgs_filled;i--;)
{ unsigned b=msgs[i].file;
if(TBUL_READ(b))
if(msgs[i].mtime-si.lastsuccess>=0)
SBUL_UNREAD(b);
else
{ msgs[i]=msgs[--priv_msgs_filled];msgs[i].order=i;
--msgs_filled;
}
}
}
}
scanfile(-1,fp,agecutoff);
if(si.brokenstate>0)
hsort(msgs,msgs_filled,sizeof*msgs,mcmp,mswap);
totmsgs=msgs_filled;
return 1;
}
void checkbulletins()
{ if(!chdir(bulletins_path))
{ struct stat stbuf;static time_t lastmods;static int nextextrafile=-1;
if(nextextrafile>=0)
{ if(!stat(bullname(nextextrafile),&stbuf)&&stbuf.st_mtime>lastmods)
goto scanem;
}
else
{ int bul;
scanem: for(msgs_filled=0,bul=MAXBULLETINS;bul--;)
if(!stat(bullname(bul),&stbuf))
{ time_t mtime=stbuf.st_mtime;
if(!lastmods||mtime-lastmods>0)
lastmods=mtime;
scanfile(bul,0,mtime);
}
priv_msgs_filled=msgs_filled;
}
if(nextextrafile<=0)
nextextrafile=MAXBULLETINS;
nextextrafile--;
}
chdir(ROOTDIR);
}
static int rread(const int fd,char*a,const unsigned len)
{ int i;size_t left=len;
do
while(0>(i=read(fd,a,left))&&errno==EINTR);
while(i>0&&(a+=i,left-=i));
return len-left;
}
static int rwrite(const int fd,char*a,const unsigned len)
{ int i;size_t left=len;
do
while(0>(i=write(fd,a,left))&&errno==EINTR);
while(i>0&&(a+=i,left-=i));
return len-left;
}
#ifdef UIDL
static void printuidl(size_t i)
{ size_t sz=MSGS(i).virtsize-hostnduser-MSGS(i).statskip;
(sizeof MSGS(i).uidl+sizeof MSGS(i).virtsize)*2>UIDLLEN?
fail("uidl too long"),terminate(EX_SOFTWARE),0:0;
fprintf(sockout,"%ld %s",(long)(i+1),Bintohex(MSGS(i).uidl,1));
fprintf(sockout,"%s\r\n",Bintohex(sz,1));
}
#endif
void printusg(void)
{ fprintf(stderr,USAGE,cucipopn);
}
main(int argc,const char*const argv[])
{ unsigned loginattempts=0,quiet=0,debug=0;short port=POP3_PORT;
unsigned douser=1,douidl=1,doapop=1,dotop=1,autodel=0,sabotage=0;
static const char tdotnl[]=".\r\n";time_t agecutoff=-AGETOLERANCE;
openlog(cucipopn,LOG_PID,LOG_FACILITY);sockin=stdin;sockout=stdout;
if(!(msgs=malloc(GROWSTEP*sizeof*msgs)))
outofmem();
msgs_max=GROWSTEP;
#ifdef SIGXCPU
signal(SIGXCPU,SIG_IGN);
#endif
#ifdef SIGXFSZ
signal(SIGXFSZ,SIG_IGN);
#endif
if(argc)
{ const char*chp;
#define Qnext_arg() if(!*chp&&!(chp=*++argv))goto usg
while(chp=*++argv)
{ if(*chp++!='-')
goto usg;
for(;;)
{ switch(argc=*chp++)
{ case DEBUG:debug=1;
continue;
case QUIET:quiet=1;
continue;
case AUDIT:audit=1;
continue;
case AUTODELETE:autodel=1;
continue;
case BERKELEYFORMAT:berkeleyformat=1;
continue;
case SABOTAGE_UIDL:sabotage=1;
continue;
case USERF:douser=0;
continue;
case UIDLF:douidl=0;
continue;
case APOPF:doapop=0;
continue;
case TOPF:dotop=0;
continue;
case FVERSION:
fprintf(stderr,"%s %s, Cubic Circle's POP3 daemon (RFC1939)"
FULLVERSION
"Locking strategies:\t"
#ifdef USEdot_lock
"dotlocking"
#endif
#ifdef USEfcntl_lock
" fcntl()"
#endif
#ifdef USElockf
" lockf()"
#endif
#ifdef USEflock
" flock()"
#endif
"\nOptional commands:\t"
#ifdef USER
"USER/PASS"
#endif
#ifdef APOP
" APOP"
#endif
#ifdef TOP
" TOP"
#endif
#ifdef UIDL
" UIDL"
#endif
"\nSystem mailbox:\t\t%s\n"
#ifdef USE_DB
"Virtual pop database:\t%s\n"
"Virtual pop user:\t%s\n"
"Housekeeping tree:\t%s\n"
"Bulletin subdirectory:\t%s\t00-%02d\n"
"State database:\t\t%s/%s\n"
#endif
,cucipopn,version,
auth_mailboxname(auth_finduid(getuid(),0)),virtualhostdb,
virtualuser,cplib,bulletins_path,MAXBULLETINS-1,cplib,
statedbfile);
return EXIT_SUCCESS;
usg: default:printusg();
return EX_USAGE;
case HELP1:case HELP2:printusg();
fprintf(stderr,XUSAGE,POP3_PORT);
return EX_USAGE;
case EXPIRE:Qnext_arg();agecutoff=atosec(chp,&chp);
continue;
case PORT:Qnext_arg();port=strtol(chp,(char**)&chp,10);
continue;
case '\0':;
}
break;
}
}
}
;{ namelen=sizeof peername;
if(getpeername(fileno(sockin),(struct sockaddr*)&peername,&namelen)&&
!debug&&(errno==ENOTSOCK||errno==EINVAL))
{ int serverfd,curfd;
signal(SIGHUP,SIG_IGN);signal(SIGPIPE,SIG_IGN);fclose(stdin);
fclose(stdout);serverfd=socket(PF_INET,SOCK_STREAM,TCP_PROT);
#ifdef INET6
/* XXX assumes INET */
memset(&peername, 0, sizeof(peername));
((struct sockaddr_in *)&peername)->sin_family = AF_INET;
((struct sockaddr_in *)&peername)->sin_port = htons(port);
#else
peername.sin_family=AF_INET;peername.sin_addr.s_addr=INADDR_ANY;
peername.sin_port=htons(port);
#endif
namelen = sizeof(struct sockaddr_in);
curfd=-1;
setsockopt(serverfd,SOL_SOCKET,SO_REUSEADDR,&curfd,sizeof curfd);
if(bind(serverfd,(struct sockaddr*)&peername,namelen))
{ syslog(LOG_CRIT,"unable to bind socket %d",POP3_PORT);
fprintf(stderr,"%s: Can't bind socket %d\n",cucipopn,POP3_PORT);
return EX_OSFILE;
}
fclose(stderr);
if(fork()>0)
return EXIT_SUCCESS;
setsid();listen(serverfd,LISTEN_BACKLOG);
sprintf(linebuf,PIDFILE,cucipopn);
;{ FILE*pidf;
if(pidf=fopen(linebuf,"w"))
fprintf(pidf,"%ld\n",(long)getpid()),fclose(pidf);
else
syslog(LOG_ERR,"can't write %s",linebuf);
}
initappdb();exitappdb(); /* prime database system */
do
{ close(curfd);
if(namelen<0)
sleep(LOCKSLEEP);
namelen=sizeof peername;checkbulletins();zombiecollect();
if((curfd=accept(serverfd,(struct sockaddr*)&peername,&namelen))<0)
continue;
zombiecollect();
}
while(namelen=fork());
close(serverfd);
if(!(sockout=fdopen(curfd,"wb"))||!(sockin=fdopen(curfd,"rb")))
if(errno==ENOMEM)
outofmem();
else
terminate(EX_PROTOCOL);
}
else
checkbulletins(),fclose(stderr);
}
chdir(bulletins_path);tsessionstart=time((time_t*)0);setsignals();
;{ struct utsname name;
uname(&name);
;{ size_t hlen;
if(!(host=malloc(hlen=strlen(name.nodename)+1))||
!(timestamp=malloc(1+sizeof(pid_t)*2+sizeof(time_t)*2+1+hlen+1)))
outofmem();
}
;{ pid_t pid=getpid();
sprintf(timestamp,"<%s",Bintohex(pid,1));
}
if(sabotage)
tsabotage=tsessionstart;
agecutoff=tsessionstart-agecutoff;
sprintf(timestamp+1+sizeof(pid_t)*2,"%s@%s>",Bintohex(tsessionstart,1),
strcpy(host,name.nodename));
}
fprintf(sockout,"+OK Cubic Circle's %s POP3 ready %s"
#ifdef LOG_FAILED_PASSWORD
"."
#endif
"\r\n",version,
timestamp);
for(;;)
{ const auth_identity*pass;
readcommand(0);
if(!arg1&&cmpbuf("quit"))
{ fprintf(sockout,"+OK Not really your day, is it?\r\n");
return EXIT_SUCCESS;
} /* still allow for trailing spaces */
if(douser&&(!arg2||!*arg2)&&cmpbuf("user"))
if(!*user)
#ifdef USER
if(arg1&&*arg1)
fprintf(sockout,"+OK %s selected\r\n",strcpy(user,arg1));
else
#endif
fail("Invalid username");
else
goto aluser;
else if(cmpbuf("pass"))
{ if(*user)
{ if(arg2)
arg2[-1]=' '; /* put back a literal space in the passwd */
if(arg1&&*arg1&&(pass=checkpw(arg1)))
{ wipestring(arg1);
goto okpass;
}
#ifdef LOG_FAILED_PASSWORD
if(audit)
syslog(LOG_DEBUG,"Password attempted: \"%s\"",arg1?arg1:"");
#endif
goto failpass;
}
fail("Select a username first");
goto wipeuser;
}
else if(doapop&&cmpbuf("apop"))
{ if(!*user)
{ if(arg1&&*arg1&&arg2&&*arg2)
{ strcpy(user,arg1);
if(pass=checkchallenge(arg2))
okpass: { free(timestamp);
hostnduser=strlen(host)+strlen(Vuser);
if(openmailbox(pass,agecutoff))
fprintf(sockout,"+OK Congratulations!\r\n");
else
{ syslog(LOG_ALERT,"Error opening %s's mailbox",Vuser);
fail("Error opening your mailbox");terminate(EX_NOINPUT);
}
break;
}
else
failpass: { syslog(LOG_ERR,"authentication failure %s %s",Vuser,peer());
sleep(++loginattempts);
fprintf(sockout,
"-ERR %s: Invalid password or username (check case)\r\n",
cucipopn);
}
}
else
fail("Pop me harder");
wipeuser: *user='\0';
}
else
aluser: fail("Already selected a username");
}
else if(arg1&&!arg2&&cmpbuf("auth")&&!strcmp(arg1,"twinkie"))
{ fail("Forget those... Try oreo cookies (and nice red uniforms :-)");
if(audit)
loginvalcmd();
}
else
invalcmd(
#ifdef USER
"USER name, PASS string, "
#endif
#ifdef APOP
"APOP name digest, "
#endif
"QUIT");
}
for(;;)
{ if(!readcommand(autodel))
goto prequitstate;
if(!arg1&&cmpbuf("quit"))
break;
if(!arg1&&cmpbuf("stat"))
fprintf(sockout,"+OK %ld %ld\r\n",(long)totmsgs,totsize);
else if(!arg2&&cmpbuf("list"))
{ if(arg1)
{ size_t i;
if(i=findmsg())
fprintf(sockout,"+OK %ld %ld\r\n",
(long)i,MSGS(i-1).virtsize);
else
goto nomsg;
}
else
{ size_t i;
fprintf(sockout,"+OK %ld messages (%ld octets)\r\n",(long)totmsgs,
(long)totsize);
for(i=0;i<msgs_filled;i++)
if(!(MSGS(i).deleted&1))
fprintf(sockout,"%ld %ld\r\n",(long)(i+1),
MSGS(i).virtsize);
goto tdot;
}
}
else if(!arg2&&cmpbuf("retr")
#ifdef TOP
||dotop&&arg2&&cmpbuf("top")
#endif
)
{ size_t i;FILE*fp;struct msg*pvm;
if((i=findmsg())&&(fp=openbulletin((pvm=&MSGS(--i))->file,fmbox)))
{ int newline=1,lines=-1,inbody=0;size_t dodel=0;
alarm(0);fseek(fp,pvm->start,SEEK_SET);
if(STRLEN(From_)!=fread(linebuf,1,STRLEN(From_),fp)||
memcmp(From_,linebuf,STRLEN(From_)))
corrupted(pvm->start);
if(arg2)
lines=strtol(arg2,(char**)0,10);
else
{ transfmsgs++;
if(autodel)
dodel=i+1;
}
fprintf(sockout,"+OK %ld octets%s\r\n",pvm->virtsize,
dodel?" (autodeleted)":"");
transfsize+=pvm->virtsize;i=pvm->realsize-STRLEN(From_);
;{ time_t lasttime;
lasttime=time((time_t*)0);
fprintf(sockout,Received,host,Vuser,cucipopn,version,
ctime(&lasttime));
}
fprintf(sockout,XFrom_);
while(i--)
{ unsigned c;
switch(c=getc(fp))
{ case '\n':
if(newline)
inbody=1;
if(EOF==fputc('\r',sockout))
{ if(autodel)
prequitstate: { autodel=2;
for(i=0;i<msgs_filled;i++)
MSGS(i).deleted&=2;
goto quitstate;
}
terminate(EX_PROTOCOL);
}
if(inbody&&!lines--) /* correct transfsize, not */
{ fputc('\n',sockout);transfsize-=i; /* entirely */
goto tdot; /* accurate, but will do */
}
newline=1;
break;
case '.':
if(newline)
fputc('.',sockout);
default:newline=0;
}
putc(c,sockout);
}
fprintf(sockout,tdotnl);
if(dodel&&MSGS(dodel-1).file<0&&MSGS(dodel-1).tooold&&
!fflush(sockout))
MSGS(dodel-1).deleted|=2;
if(fp!=fmbox)
fclose(fp);
}
else
goto nomsg;
}
else if(!arg2&&cmpbuf("dele"))
{ size_t i;
if(i=findmsg())
{ fprintf(sockout,"+OK Message %ld deleted\r\n",(long)i--);
MSGS(i).deleted|=1;totmsgs--;totsize-=MSGS(i).virtsize;
}
else
goto nomsg;
}
#ifdef LAST_HACK
else if(!arg1&&cmpbuf("last")) /* LAST does not exist anymore */
{ fprintf(sockout,"+OK 0\r\n");
syslog(LOG_DEBUG,"Faked LAST for %s",Vuser);
}
#endif
else if(!arg1&&cmpbuf("noop"))
fprintf(sockout,"+OK\r\n");
else if(!arg1&&cmpbuf("rset"))
{ size_t i;
for(i=0;i<msgs_filled;i++)
if(MSGS(i).deleted&1)
totmsgs++,totsize+=MSGS(i).virtsize,MSGS(i).deleted&=2;
fprintf(sockout,"+OK %ld messages (%ld octets)\r\n",(long)totmsgs,
(long)totsize);
}
else if(douidl&&!arg2&&cmpbuf("uidl"))
{ if(arg1)
{ size_t i;
if(i=findmsg())
#ifdef UIDL
fprintf(sockout,"+OK "),printuidl(i-1);
#else
notimpl: fail("Command not implemented");
#endif
else
nomsg: fail("No such message");
}
else
{ size_t i;
#ifndef UIDL
goto notimpl;
#else
fprintf(sockout,
"+OK But remember to DELETE messages REGULARLY\r\n");
for(i=0;i<msgs_filled;i++)
if(!(MSGS(i).deleted&1))
printuidl(i);
#endif
tdot: fprintf(sockout,tdotnl);
}
}
else
invalcmd("STAT, LIST [msg], RETR msg, "
#ifdef TOP
"TOP msg n, "
#endif
"DELE msg, "
#ifdef UIDL
"UIDL [msg], "
#endif
"NOOP, RSET, QUIT");
}
quitstate:
;{ off_t target=0;size_t i;
for(i=priv_msgs_filled;i<msgs_filled&&!msgs[i].deleted;i++);
if(i<msgs_filled)
{ char*buf;off_t source;size_t slen;int fd=fileno(fmbox);
alarm(0);setbuf(sockin,(char*)0);setbuf(fmbox,(char*)0);free(host);
if(!(buf=malloc(COPYBUF)))
outofmem();
if(!lockmbox(fd,mailbox))
{ syslog(LOG_ALERT,"Error locking %s's mailbox",Vuser);
fail("Error locking your mailbox");terminate(EX_NOINPUT);
}
;{ struct msg*msp=msgs+msgs_filled-1;
addblock(msp->start+msp->realsize);
}
if(oldend>(source=lseek(fd,(off_t)0L,SEEK_END)))
goto corrupt;
addblock(source);msgs[--msgs_filled].deleted=1;
for(target=msgs[i].start;i<msgs_filled;i++)
if(!msgs[i].deleted)
{ size_t readin;
source=msgs[i].start;
while(!msgs[++i].deleted);
if(slen=msgs[i].start-source)
{ lseek(fd,source,SEEK_SET);
if((readin=rread(fd,buf,slen>COPYBUF?(size_t)COPYBUF:slen))<
STRLEN(From_)||
memcmp(From_,buf,STRLEN(From_)))
goto corrupt;
goto jin;
do
{ lseek(fd,source,SEEK_SET);
if(!(readin=
rread(fd,buf,slen>COPYBUF?(size_t)COPYBUF:slen)))
corrupt: corrupted(source);
jin: source+=readin;lseek(fd,target,SEEK_SET);
if(readin!=rwrite(fd,buf,readin))
goto writerr;
target+=readin;
}
while(slen-=readin);
}
}
free(buf);lseek(fd,(off_t)0L,SEEK_SET);
if(ftruncate(fd,target))
goto writerr;
unlockmbox();
if(close(fd))
writerr: { syslog(LOG_CRIT,"write error mailbox %s at %ld",Vuser,(long)target);
fail("Error writing mailbox");terminate(EX_OSERR);
}
}
if(!quiet)
logfinal(LOG_INFO,autodel==2,autodel);
fprintf(sockout,"+OK Was it as good for you, as it was for me? (");
fprintf(sockout,target?">%ld bytes left)\r\n":
"clean as a baby)\r\n",(long)target);
}
;{ unsigned i;
for(i=priv_msgs_filled;i--;)
if(msgs[i].deleted&1)
{ unsigned b=msgs[i].file;
SBUL_READ(b);
}
}
si.lastsuccess=tsessionstart;
if(transfmsgs)
si.brokenstate=0;
if(!si.lastabort)
si.lastabort=tsessionstart;
blocksignals();putstate(Vuser,&si);closedb();exitappdb();
return EXIT_SUCCESS;
}
syntax highlighted by Code2HTML, v. 0.9.1