/************************************************************************
 *	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, S.R. van den Berg, The Netherlands		*
 *	#include "README"						*
 ************************************************************************/
#ifdef RCS
static /*const*/char rcsid[]=
 "$Id: cucipop.c,v 1.8 1996/12/26 17:23: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>

#include "cucipop.h"
#include "authenticate.h"
#include "locking.h"

#ifdef APOP
#include "md5/md5.h"
#endif

#define MD5_DIGLEN	16
#define BIN2HEXLEN	(MD5_DIGLEN*2)
#define Signal(sig,fn)	signal(sig,(void(*)())(fn))

#define Bintohex(arg)	(sizeof(arg)>MD5_DIGLEN?\
 (fail(#arg" too large"),terminate(EX_SOFTWARE),""):\
 bintohex(&(arg),sizeof(arg)))

static const char*const nullp,From_[]=FROM,XFrom_[]=XFROM,cucipopn[]="cucipop",
 Received[]=
  "Received: from %s for %s\r\n with Cubic Circle's %s (%s) %.24s\r\n",
 version[]=VERSION;
static char linebuf[LINEBUFLEN+1],*arg1,*arg2,user[ARGLEN+1],*host,*timestamp;
static FILE*fmbox,*sockin,*sockout;
static struct sockaddr_in peername;

struct msg
 { off_t start;
#ifdef UIDL
   unsigned long uidl;
#endif
   long virtsize;
   unsigned char deleted;
 }*msgs;

static size_t msgs_filled,msgs_max,totmsgs,transfmsgs;
static long totsize,transfsize;
static off_t oldend;
static unsigned audit,berkeleyformat;
static time_t tsabotage;
static const char*mailbox;

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(SIGURG,SIG_IGN);signal(SIGALRM,SIG_IGN);
}

void setsignals(void)
{ Signal(SIGHUP,bailout);Signal(SIGINT,bailout);Signal(SIGTERM,bailout);
  Signal(SIGPIPE,bailout);Signal(SIGURG,bailout);Signal(SIGALRM,bailout);
}

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)
{ static char hex[BIN2HEXLEN+1],*hp;
  for(hp=hex;size--;ptr=(char*)ptr+1,hp+=2)
     sprintf(hp,"%02x",*(unsigned char*)ptr);
  return hex;
}

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);
}

static void terminate(int code)
{ unlockmbox();
  if(!*user||transfsize||totmsgs||audit)
     syslog(LOG_NOTICE,"lost %s %s, %ld (%ld), %ld (%ld)",user,peer(),
      (long)transfmsgs,transfsize,(long)totmsgs,totsize);
  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(int 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*checkpw(const char*pw)
{ const auth_identity*pass;
  if(!(pass=auth_finduser(user,fileno(sockin)))||auth_whatuid(pass)<LOWEST_UID)
     goto ret0;
  if(!auth_checkpassword(pass,pw,0))
ret0:
     return 0;
  return setuid(auth_whatuid(pass))?(auth_identity*)0: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=auth_getsecret(pass))||!*secret)
	   goto ret0;
	if(!(timestamp=realloc(timestamp,
	 (seclen=strlen(secret))+(stlen=strlen(timestamp))+1)))
	   outofmem();
	strcpy(timestamp+stlen,secret);wipestring(secret);
      }
     ;{ MD5_CTX context;unsigned char digest[MD5_DIGLEN];
	MD5Init(&context);MD5Update(&context,(unsigned char*)timestamp,seclen);
	wipestring(timestamp+stlen);MD5Final(digest,&context);
	if(!strcmp(Bintohex(digest),ch))
	   return setuid(auth_whatuid(pass))?(auth_identity*)0: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)*sizeof(struct msg))))
     outofmem();
  msgs[msgs_filled].start=start;msgs[msgs_filled].virtsize=0;
  msgs[msgs_filled++].deleted=0;
}

static int openmailbox(const auth_identity*pass)
{ off_t start,end;size_t virtsize,extra;unsigned long uidl;unsigned inheader;
  long clen;int fdsockin;long spoofcheck=0;
  alarm(0);clen=end=start=0;uidl=tsabotage;fdsockin=fileno(sockin);
  if(!(fmbox=fopen(mailbox=auth_mailboxname((auth_identity*)pass),"r+b")))
   { if(errno==ENOENT)
	goto nomailbox;					 /* no mailbox found */
     return 0;
   }
  ungetc(fgetc(fmbox),fmbox);				      /* prime atime */
  ;{ struct stat stbuf;int fd;
     fstat(fd=fileno(fmbox),&stbuf);
     if(stbuf.st_atime-stbuf.st_mtime<32)	/* modification in progress? */
      { if(!lockmbox(fd,mailbox))
	   return 0;
	fstat(fd,&stbuf);unlockmbox();setsignals();
      }
     oldend=end=stbuf.st_size;
   }
  virtsize=extra=STRLEN(XFrom_)-STRLEN(From_)+STRLEN(Received)+strlen(host)-2+
   strlen(user)-2+STRLEN(cucipopn)-2+STRLEN(version)-2+24-5;
  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(fmbox)))
	    { ungetc(c,fmbox);end++;
	      break;
	    }
	   newlines=2;
	   if(!*++p)
	    { inheader=1;addblock(start);msgs[msgs_filled-1].deleted=0;
	      if(msgs_filled>1)
	       { totsize+=msgs[msgs_filled-2].virtsize=
		  virtsize+start-msgs[msgs_filled-2].start;
#ifdef UIDL
		 msgs[msgs_filled-2].uidl=uidl;
#endif
	       }
	      uidl=tsabotage;virtsize=extra;
	      break;
	    }
	 }
	start+=p-From_;
      }
nomailbox:
     ;{ off_t oend;register int c;
	oend=end;
	do
	 { if(!end--)
	      goto thatsall;
	   if(!inheader)
	      clen--;
	   c=getc(fmbox);
	   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')
	    { virtsize++;
	      if(--newlines&&inheader&&berkeleyformat)
	       { static const char contlength[]=CONTLENGTH;const char*p;
		 for(p=contlength;;)
		  { if(!end)
		       goto thatsall;
		    end--;
		    if(*p!=tolower(c=getc(fmbox)))
		       goto trynl;
		    newlines=2;
		    if(!*++p)
		     { for(;;)
			{ if(!end)
			     goto thatsall;
			  end--;
			  switch(c=fgetc(fmbox))
			   { case ' ':case '\t':continue;
			   }
			  break;
			}
		       for(clen=0;;end--,c=fgetc(fmbox))
			{ if(c<'0'||c>'9')
			     goto trynl;
			  clen=((unsigned long)clen*10)+c-'0';
			  if(!end)   /* memory requirements to a perfect fit */
thatsall:		   { fcntl(fdsockin,F_SETFL,0L);      /* block again */
			     if(clen>0)
				syslog(LOG_WARNING,"%s mailbox %ld short",
				 mailbox,(long)clen);
			     if(!(msgs=realloc(msgs,(msgs_max=
			      (totmsgs=msgs_filled)+2)*sizeof(struct msg))))
				outofmem();
			     addblock(start+=oend);
			     msgs[--msgs_filled].deleted=1;
			     if(msgs_filled>0)
			      { totsize+=msgs[msgs_filled-1].virtsize=
				 virtsize+start-msgs[msgs_filled-1].start;
#ifdef UIDL
				msgs[msgs_filled-1].uidl=uidl;
#endif
			      }
			     return 1;
			   }
			}
		     }
		  }
	       }
	      if(!newlines&&!inheader&&clen>0)
		 newlines=1;
	    }
	   else
	      newlines=2;
	 }
	while(newlines);
	inheader=0;start+=oend-end;
      }
   }
}

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)
{ (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));
  fprintf(sockout,"%s\r\n",Bintohex(msgs[i].virtsize));
}
#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";
  chdir(ROOTDIR);openlog(cucipopn,LOG_PID,LOG_MAIL);sockin=stdin;
  sockout=stdout;
  if(!(msgs=malloc(GROWSTEP*sizeof(struct msg))))
     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
		  "\n",cucipopn,version);
		 return EXIT_SUCCESS;
usg:	      default:printusg();
		 return EX_USAGE;
	      case HELP1:case HELP2:printusg();
		 fprintf(stderr,XUSAGE,POP3_PORT);
		 return EX_USAGE;
	      case PORT:Qnext_arg();port=strtol(chp,(char**)&chp,10);
		 continue;
	      case '\0':;
	    }
	   break;
	 }
      }
   }
  ;{ int 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(AF_INET,SOCK_STREAM,TCP_PROT);
	peername.sin_family=AF_INET;peername.sin_addr.s_addr=INADDR_ANY;
	peername.sin_port=htons(port);curfd=-1;
	setsockopt(serverfd,SOL_SOCKET,SO_REUSEADDR,&curfd,sizeof curfd);
	if(bind(serverfd,(struct sockaddr*)&peername,sizeof peername))
	 { 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);
	 }
	do
	 { close(curfd);
	   if(namelen<0)
	      sleep(LOCKSLEEP);
	   namelen=sizeof peername;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
	fclose(stderr);
   }
  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+hlen+1)))
	   outofmem();
      }
     ;{ pid_t pid=getpid();
	sprintf(timestamp,"<%s",Bintohex(pid));
      }
     ;{ time_t t=time((time_t*)0);
	if(sabotage)
	   tsabotage=t;
	sprintf(timestamp+1+sizeof(pid_t)*2,"%s@%s>",Bintohex(t),
	 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);
		 if(openmailbox(pass))
		    fprintf(sockout,"+OK Congratulations!\r\n");
		 else
		  { syslog(LOG_ALERT,"Error opening %s's mailbox",user);
		    fail("Error opening your mailbox");terminate(EX_NOINPUT);
		  }
		 break;
	       }
	      else
failpass:      { syslog(LOG_ERR,"authentication failure %s %s",user,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;
	if(i=findmsg())
	 { int newline=1,lines=-1,inbody=0;size_t dodel=0;
	   alarm(0);fseek(fmbox,msgs[--i].start,SEEK_SET);
	   if(STRLEN(From_)!=fread(linebuf,1,STRLEN(From_),fmbox)||
	    memcmp(From_,linebuf,STRLEN(From_)))
	      corrupted(msgs[i].start);
	   if(arg2)
	      lines=strtol(arg2,(char**)0,10);
	   else
	    { transfmsgs++;
	      if(autodel)
		 dodel=i+1;
	    }
	   fprintf(sockout,"+OK %ld octets%s\r\n",msgs[i].virtsize,
	    dodel?" (autodeleted)":"");
	   transfsize+=msgs[i].virtsize;
	   i=msgs[i+1].start-msgs[i].start-STRLEN(From_);
	   ;{ time_t lasttime;
	      lasttime=time((time_t*)0);
	      fprintf(sockout,Received,host,user,cucipopn,version,
	       ctime(&lasttime));
	    }
	   fprintf(sockout,XFrom_);
	   while(i--)
	    { unsigned c;
	      switch(c=getc(fmbox))
	       { 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&&!fflush(sockout))
	      msgs[dodel-1].deleted|=2;
	 }
	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",user);
      }
#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				 /* UIDL will change with endianness */
	      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;
     if(msgs_filled)
      { char*buf;off_t source;size_t slen,i;int fd=fileno(fmbox);
	alarm(0);setbuf(sockin,(char*)0);setbuf(fmbox,(char*)0);free(host);
	if(!lockmbox(fd,mailbox))
	 { syslog(LOG_ALERT,"Error locking %s's mailbox",user);
	   fail("Error locking your mailbox");terminate(EX_NOINPUT);
	 }
	if(!(buf=malloc(COPYBUF)))
	   outofmem();
	msgs[msgs_filled++].deleted=0;
	if(oldend>(source=lseek(fd,(off_t)0L,SEEK_END)))
	   goto corrupt;
	addblock(source);msgs[--msgs_filled].deleted=1;
	for(i=0;i<msgs_filled&&!msgs[i].deleted;i++);
	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",user,(long)target);
	   fail("Error writing mailbox");terminate(EX_OSERR);
	 }
      }
     if(!quiet&&(transfsize||totmsgs||audit))
	syslog(LOG_INFO,"%s%s %s, %s%ld (%ld), %ld (%ld)",
	 autodel==2?"lost ":"",user,peer(),autodel?"-":"",(long)transfmsgs,
	 transfsize,(long)totmsgs,totsize);
     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);
   }
  return EXIT_SUCCESS;
}
