/************************************************************************
 *	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