1/************************************************************************
2 *	LMTP (Local Mail Transfer Protocol) routines			*
3 *									*
4 *	Copyright (c) 1997-2001, Philip Guenther, The United States	*
5 *							of America	*
6 *	#include "../README"						*
7 ************************************************************************/
8#ifdef RCS
9static /*const*/char rcsid[]=
10 "$Id: lmtp.c,v 1.13 2001/06/28 21:44:28 guenther Exp $"
11#endif
12#include "procmail.h"
13#ifdef LMTP
14#include "sublib.h"
15#include "robust.h"
16#include "misc.h"
17#include "shell.h"
18#include "authenticate.h"
19#include "cstdio.h"
20#include "mailfold.h"
21#include "memblk.h"
22#include "from.h"
23#include "lmtp.h"
24
25/*
26
27lhlo		=> print stuff
28mail from:	=> fork
29		   Generate From_ line (just body type)
30rcpt to:	=> deduce pass entry, push it somewhere
31data		=> process data till end, if we run out of mem, give 552
32			(or 452?) response
33bdat		=> allocate requested size buffer & read it in, or 552/452
34			reponse.
35vrfy		=> just check the user
36end of data	=> fork, perform deliveries, giving error codes as you go.
37rset		=> if in child, die.
38
39
40	Should this simply be a _conformant_ implementation or should it
41	be _strict_?  For example, after accepting a BDAT, should a DATA
42	command cause LMTP to not accept anything but a RSET?  As it stands
43	we'll be lenient and simply ignore invalid commands.  It's undefined
44	behavior, so we can do what we want.
45*/
46
47#define INITIAL_RCPTS	10
48#define INCR_RCPTS	20
49
50static int lreaddyn P((void));
51
52int childserverpid;
53
54static ctopfd;
55static char*overread;
56static size_t overlen;
57
58static int nliseol;			 /* is a plain \n the EOL delimiter? */
59static char*bufcur;
60static const char
61 lhlomsg[]=
62"250-localhost\r\n\
63250-SIZE\r\n\
64250-8BITMIME\r\n\
65250-PIPELINING\r\n\
66250-CHUNKING\r\n\
67250 ENHANCEDSTATUSCODES\r\n",
68 nomemmsg[]="452 4.3.0 insufficient system storage\r\n",
69 quitmsg[]="221 2.3.0 Goodbye!\r\n",
70 baduser[]="550 5.1.1 mailbox unknown\r\n",
71 protogood[]="250 2.5.0 command succeeded\r\n",
72 protobad[]="503 5.5.1 command unexpected\r\n";
73
74static void bufwrite(buffer,len,flush)
75const char*buffer;int len;int flush;
76{ int already;
77  if((already=bufcur-buf2)+len>linebuf||flush)
78   { if(already&&already!=rwrite(savstdout,bufcur=buf2,already)||
79      len&&len!=rwrite(savstdout,buffer,len))
80	exit(EX_OSERR);
81   }
82  else
83     bufcur=(char*)tmemmove(bufcur,buffer,len)+len;
84}
85#define bufinit		bufcur=buf2
86#define skiptoeol	do c=getL(); while(c!='\n');  /* skip to end-o'-line */
87
88static int unexpect(str)const char*str;
89{ char c;
90  while(*str!='\0')
91   { if((c=getL())-'a'<='z'-'a'&&c>='a')c-='a'-'A';
92     if(c!= *str)
93      { if(c!='\n')
94	   skiptoeol;
95	return 1;
96      }
97     str++;
98   }
99  return 0;
100}
101#define NOTcommand(command,offset)	unexpect((msgcmd=command)+offset)
102
103static long slurpnumber P((void))
104{ int c;long total=0;		/* strtol would require buffering the number */
105  while((c=getL())-'0'>=0&&c-'0'<10)
106   { total*=10;
107     total+=c-'0';
108   }
109  if(c!=' '&&c!='\n'&&c!='\r')
110   { skiptoeol;
111     return -1;
112   }
113  if(c=='\n')
114   { ungetb(c);
115   }
116  return total;
117}
118
119/* Slurp "<.*?>" with rfc821 quoting rules, strip the trailing angle bracket */
120/* This is stricter than it needs to be, but not as strict as the rfcs */
121static char *slurpaddress P((void))
122{ char*p=buf,c,*const last=buf+linebuf-1;     /* -1 to leave room for the \0 */
123  do{*p=getL();}while(*p==' ');
124  if(*p!='<')
125   { if(*p!='\n')
126	skiptoeol;
127     return 0;
128   }
129  while(++p<last)
130     switch(*p=getL())
131      { case '\\':
132	   if(++p==last)
133	      goto syntax_error;
134	   *p++=getL();
135	   continue;
136	case '"':
137	   while(++p<last)
138	      switch(*p=getL())
139	       { case '\\':
140		    if(++p==last)
141		       goto syntax_error;
142		    *p=getL();
143		    continue;
144		 case '"':
145		    break;
146		 case '\r':
147			goto syntax_error;
148		 case '\n':
149		    return 0;
150		 default:
151		    continue;
152	       }
153	   continue;
154	case '\00':case '\01':case '\02':case '\03':case '\04':
155	case '\05':case '\06':case '\07':case '\10':case '\11':
156		   case '\13':case '\14':case '\15':case '\16':
157	case '\17':case '\20':case '\21':case '\22':case '\23':
158	case '\24':case '\25':case '\26':case '\27':case '\30':
159	case '\31':case '\32':case '\33':case '\34':case '\35':
160	case '\36':case '\37':case '<':case '(':case ')':case ';':
161syntax_error:
162	   skiptoeol;
163	case '\n':
164	   return 0;
165	case '>':
166	   *p='\0';				   /* strip the trailing '>' */
167	   return tstrdup(buf);
168	default:
169	   break;
170      }
171  goto syntax_error;
172}
173
174/* given a path from slurpaddress() extract the local-part and strip quoting */
175static char *extractaddress(path)char *path;
176{ char *p=path+1,*q=path;		       /* +1 to skip the leading '<' */
177  if(*p=='@')		    /* route address.  Perhaps we should assume that */
178     while(1)			/* sendmail will strip these for recipients? */
179	switch(*++p)
180	 { case ':':p++;       /* we can take some shortcuts because we know */
181	      goto found;		/* the quoting and length to be okay */
182	   case '\\':p++;
183	      break;
184	   case '"':
185	      while(*++p!='"')
186		 if(*p=='\\')
187		    p++;
188	      break;
189	   case '>':case '\0':
190	      free(path);				    /* no local part */
191	      return 0;
192	 }
193found:
194  while(1)						    /* strip quoting */
195   { switch(*p)
196      { case '\\':
197	   p++;
198	   break;
199	case '"':
200	   while(*++p!='"')
201	      if(*p=='\\')
202		 *q++= *++p;
203	      else
204		 *q++= *p;
205	   p++;
206	   continue;
207	case '\0':case '>':	 /* no final host part?	 That's fine with us */
208	case '@':
209	   *q='\0';
210	   return path;
211      }
212     *q++= *p++;
213   }
214}
215
216/*
217linebuf MUST be at least 256, and should be at least 1024 or so for buffering
218*/
219
220/* LMTP connection states */
221#define S_START		0		/* lhlo => S_MAIL */
222#define S_MAIL		1		/* mail => S_RCPT */
223#define S_RCPT		2		/* data => S_MAIL, bdat => S_BDAT */
224#define S_BDAT		3		/* bdat last => S_MAIL */
225
226/* lmtp: run the LTMP protocol.	 It returns in children, never the
227   parent.  The return value is the array of recipients, and into the
228   first argument is stored a pointer to one past the end of that
229   array.  the second argument should be the username of the person
230   running procmail (this is used to generate the From_ line)  The
231   returning child should handle the deliveries, calling lmtpresponse()
232   with the exitcode of each one, then write 'overlen' bytes from
233   'overread' to 'ctopfd', and exit.  If something unrecoverable goes
234   wrong, and it can't do the necessary calls to lmtpresponse(), then
235   it should exit with some non-zero status.  The parent will then
236   syslog it, and exit with EX_SOFTWARE.  (See getL() in cstdio.c) */
237struct auth_identity **lmtp(lrout,invoker)
238struct auth_identity***lrout;char*invoker;
239{ static const char cLHLO[]="LHLO ",cMAIL[]="MAIL FROM:",cRCPT[]="RCPT TO:",
240   cDATA[]="DATA",cBDAT[]="BDAT",cRSET[]="RSET",cVRFY[]="VRFY ",cQUIT[]="QUIT",
241   cNOOP[]="NOOP";
242  const char*msg,*msgcmd;int flush=0,c,lmtp_state=S_START;long size=0;
243  auth_identity**rcpts,**lastrcpt,**currcpt;
244
245  pushfd(STDIN);overread=0;overlen=0;nliseol=1;
246  bufinit;ctopfd=-1;					 /* setup our output */
247  currcpt=rcpts=malloc(INITIAL_RCPTS*sizeof*rcpts);
248  lastrcpt=INITIAL_RCPTS+currcpt;
249  bufwrite("220 ",4,0);bufwrite(procmailn,strlen(procmailn),0);
250  bufwrite(Version,strchr(Version,'\n')-Version,0);
251  bufwrite(" LMTP\r\n",7,1);
252  while(1)
253   { do{c=getL();}while(c==' ');
254     switch(c)
255      { case 'l': case 'L':
256	   if(NOTcommand(cLHLO,1))
257	      goto unknown_command;
258	   ;{ int sawcrnl=0;		      /* autodetect \r\n vs plain \n */
259	      while((c=getL())!='\n')
260		 if(c=='\r')
261		  { c=getL();			      /* they lose on \r\r\n */
262		    if(c=='\n')
263		     { sawcrnl=1;
264		       break;
265		     }
266		  }
267	      flush=1;
268	      if(lmtp_state!=S_START)
269	       { msg=protobad;
270		 goto message;
271	       }
272	      else
273	       { lmtp_state=S_MAIL;
274		 msg=lhlomsg;msgcmd=0;
275		 if(sawcrnl)
276		    nliseol=0;
277	       }
278	    }
279	   goto message;
280	case 'm': case 'M':
281	   if(NOTcommand(cMAIL,1))
282	      goto unknown_command;
283	   ;{ int pipefds[2];char*from;
284	      if(lmtp_state!=S_MAIL)
285	       { skiptoeol;msg=protobad;
286		 goto message;
287	       }
288	      if(!(from=slurpaddress()))
289	       { msg="553 5.1.7 Unable to parse MAIL address\r\n";
290		 goto message;
291	       }
292	      size=0;
293	      goto jumpin;
294	      do
295	       { switch(c)
296		  { case 's':case 'S':
297		       if(unexpect("IZE="))			  /* rfc1653 */
298			  goto unknown_param;
299		       size=slurpnumber();
300		       if(size<0)		/* will be zerod at loop top */
301			  goto unknown_param;
302		       break;
303		    case 'b':case 'B':
304		       if(unexpect("ODY="))			  /* rfc1652 */
305			  goto unknown_param;
306		       while((c=getL())!='\r')		      /* just ignore */
307			  switch(c)		      /* the parameter as we */
308			   { case ' ':goto jumpin;	/* can't do anything */
309			     case '\n':goto jumpout;	   /* useful with it */
310			   }
311		    case '\r':
312		       if((c=getL())=='\n')
313			  continue;
314		    default:
315		       skiptoeol;
316unknown_param:	       msg="504 5.5.4 unknown MAIL parameter or bad value\r\n";
317		       goto message;
318		  }
319jumpin:		 do c=getL();
320		 while(c==' ');
321		}
322	      while(c!='\n');
323jumpout:      rpipe(pipefds);
324	      /*
325	       * This is a pipe on which to write back one byte which,
326	       * if non-zero, indicates something went wrong and the
327	       * parent should act like the MAIL FROM: never happened.
328	       * If it was zero then it should be followed by any extra
329	       * LMTP commands that the child read past what it needed.
330	       */
331	      if(!(childserverpid=sfork()))
332	       { char status=0;
333		 rclose(pipefds[0]);
334		 ctopfd=pipefds[1];
335		 bufwrite(0,0,1);	      /* free up buf2 for makeFrom() */
336		 makeFrom(from+1,invoker);
337		 /* bufinit;	only needed if buf2 might be realloced */
338		 free(from);
339		 if(size&&!resizeblock(&themail,size+=filled+3,1))/* try for */
340		  { status=1;	      /* the memory now, +3 for the "." CRLF */
341		    bufwrite(nomemmsg,STRLEN(nomemmsg),1);
342		  }
343		 if(rwrite(pipefds[1],&status,sizeof(status))!=sizeof(status))
344		    exit(EX_OSERR);
345		 if(status)
346		    exit(0);
347		 lmtp_state=S_RCPT;
348		 msg=protogood;
349		 goto message;
350	       }
351	      rclose(pipefds[1]);
352	      if(!forkerr(childserverpid,buf))
353	       { char status=1;
354		 rread(pipefds[0],&status,sizeof(status));
355		 if(!status)
356		  { pushfd(pipefds[0]);		   /* pick up what the child */
357		    lmtp_state=S_MAIL;			/* left lying around */
358		    bufinit;
359		  }
360		 continue;				     /* restart loop */
361	       }
362	      rclose(pipefds[0]);
363	      msg="421 4.3.2 unable to fork for MAIL\r\n";
364	      goto message;
365	    }
366	case 'r': case 'R':
367	   if((c=getL())=='s'||c=='S')
368	    { if(NOTcommand(cRSET,2))
369		 goto unknown_command;
370	      skiptoeol;
371	      if(lmtp_state!=S_START)
372		 lmtp_state=S_MAIL;
373	      msg=protogood;
374	      goto message;
375	    }
376	   if((c!='c'&&c!='C')||NOTcommand(cRCPT,2))
377	      goto unknown_command;
378	   if(lmtp_state!=S_RCPT)
379	    { skiptoeol;
380	      msg=protobad;
381	      /* don't change lmtp_state */
382	      goto message;
383	    }
384	   if(currcpt==lastrcpt)		    /* do I need some space? */
385	    { int num=lastrcpt-rcpts;
386	      rcpts=realloc(rcpts,(num+INCR_RCPTS)*sizeof*rcpts);
387	      currcpt=rcpts+num;lastrcpt=currcpt+INCR_RCPTS;
388	    }
389	   ;{ char *path,*mailbox;auth_identity*temp;
390		    /* if it errors, extractaddress() will free its argument */
391	      if(!(path=slurpaddress())||!(mailbox=extractaddress(path)))
392	       { msg="550 5.1.3 address syntax error\r\n";
393		 goto message;
394	       }
395/* if we were to handle ESMTP params on the RCPT verb, we would do so here */
396	      skiptoeol;
397	      if(!(temp=auth_finduser(mailbox,0)))
398	       { msg="550 5.1.1 mailbox unknown\r\n";
399		 free(path);
400		 goto message;
401	       }
402	      auth_copyid(*currcpt=auth_newid(),temp);
403	      free(path);
404	      currcpt++;
405	      msg="250 2.1.5 ok\r\n";
406	      goto message;
407	    }
408	case 'd': case 'D':
409	   flush=1;
410	   if(NOTcommand(cDATA,1))
411	      goto unknown_command;
412	   skiptoeol;
413	   if(lmtp_state!=S_RCPT)
414	    { msg=protobad;
415	      goto message;
416	    }
417	   if(currcpt==rcpts)
418	    { msg="554 5.5.1 to whom?\r\n";
419	      goto message;
420	    }
421	   msg="354 Enter DATA terminated with a solo \".\"\r\n";
422	   bufwrite(msg,strlen(msg),1);
423	   if(!(lreaddyn()))
424	    { /*
425	       * At this point we either have more data to read which we
426	       * can't fit, or, worse, we've lost part of the command stream.
427	       * The (ugly) solution/way out is to send the 452 status code
428	       * and then kill both ourselves and out parent.  That's the
429	       * only solution short of teaching lreaddyn() to take a small
430	       * buffer (buf2?) and repeatedly fill it looking for the end
431	       * of the data stream, but that's too ugly.  If the malloc
432	       * failed then the machine is probably hurting enough that
433	       * our exit can only help.
434	       */
435	      bufwrite(nomemmsg,STRLEN(nomemmsg),1);
436	      goto quit;
437	    }
438deliver:   readmail(2,0L);		/* fix up things */
439	   lastrcpt=rcpts;
440	   rcpts=realloc(rcpts,(currcpt-rcpts)*sizeof*rcpts);
441	   *lrout=(currcpt-lastrcpt)+rcpts;
442	   return rcpts;
443	case 'b': case 'B':		/* rfc1830's BDAT */
444	   if(NOTcommand(cBDAT,1))
445	      goto unknown_command;
446	   if((c=getL())!=' ')
447	    { if(c!='\n')
448		 skiptoeol;
449	      msg="504 5.5.4 octets count missing\r\n";
450	      goto message;
451	    }
452	   if(lmtp_state<S_RCPT)
453	    { msg=protobad;
454	      goto message;
455	    }
456	   if(currcpt==rcpts)
457	    { msg="554 5.5.1 to whom?\r\n";
458	      goto message;
459	    }
460	   ;{ int last=0;
461	      long length=slurpnumber();
462	      if(length<0)
463	       { msg="555 5.5.4 octet count bad\r\n";
464		 goto message;
465	       }
466	      do{c=getL();}while(c==' ');
467	      if(c=='l'||c=='L')
468	       { if(unexpect("AST"))
469		  {
470bad_bdat_param:	    msg="504 5.5.4 parameter unknown\r\n";
471		    goto message;
472		  }
473		 last=1;
474		 c=getL();
475	       }
476	      if(!nliseol&&c=='\r')
477		 c=getL();
478	      if(c!='\n')
479	       { skiptoeol;
480		 goto bad_bdat_param;
481	       }
482	      if(filled+length>size)
483	       { if(!resizeblock(&themail,size=filled+length+BLKSIZ,1))
484		  { int i;				/* eat the BDAT data */
485		    while(length>linebuf)
486		     { i=readLe(buf,linebuf);
487		       if(i<0)
488			  goto quit;
489		       length-=i;
490		     }
491		    if(length&&0>readLe(buf,length))
492		       goto quit;
493		    lmtp_state=S_MAIL;
494		    msg=nomemmsg;
495		    flush=1;
496		    goto message;
497		  }
498	       }
499	      while(length>0)
500	       { int i=readLe(themail.p+filled,length);
501		 if(!i)
502		    exit(EX_NOINPUT);
503		 else if(i<0)
504		    goto quit;
505		 length-=i;
506		 filled+=i;
507	       }
508	      if(last)
509	       { if(!nliseol)				/* change CRNL to NL */
510		  { char*in,*out,*q,*last;
511		    last=(in=out=themail.p)+filled;
512		    while(in<last)
513		       if((q=memchr(in,'\r',last-in))?q>in:!!(q=last))
514			{ if(in!=out)
515			     memmove(out,in,q-in);
516			  out+=q-in;in=q;
517			}
518		       else if(++in==last||*in!='\n')	     /* keep the CR? */
519			  *out++='\r';
520		    resizeblock(&themail,(filled-=in-out)+1,1);
521		  }
522		 goto deliver;
523	       }
524	      msg=protogood;
525	      goto message;
526	    }
527	case 'v': case 'V':
528	   if(NOTcommand(cVRFY,1))
529	      goto unknown_command;
530	   flush=1;
531	   ;{ char *path,*mailbox;
532	      auth_identity *temp;
533	      if(!(path=slurpaddress())||!(mailbox=extractaddress(path)))
534	       { msg="501 5.1.3 address syntax error\r\n";
535		 goto message;
536	       }
537	      skiptoeol;
538	      if(!(temp=auth_finduser(mailbox,0)))
539	       { msg="550 5.1.1 user unknown\r\n";
540		 free(path);
541		 goto message;
542	       }
543	      free(path);
544	      msg="252 2.5.0 successful\r\n";
545	      goto message;
546	    }
547	case 'q': case 'Q':
548	   if(NOTcommand(cQUIT,1))
549	      goto unknown_command;
550quit:	   if(ctopfd>=0)	   /* we're the kid: tell the parent to quit */
551	    { rwrite(ctopfd,cQUIT,STRLEN(cQUIT));
552	      rclose(ctopfd);
553	    }
554	   else
555	      bufwrite(quitmsg,STRLEN(quitmsg),1);
556	   exit(0);
557	case 'n': case 'N':
558	   if(NOTcommand(cNOOP,1))
559	      goto unknown_command;
560	   skiptoeol;
561	   flush=1;
562	   msg="200 2.0.0 ? Nope\r\n";
563	   goto message;
564	default:
565	   skiptoeol;
566unknown_command:
567	case '\n':
568	   msg="500 5.5.1 Unknown command given\r\n";msgcmd=0;
569	   flush=1;
570	   break;
571      }
572message:
573     bufwrite(msg,10,0);msg+=10;
574     if(msgcmd)					  /* insert the command name */
575      { msg--;
576	bufwrite(msgcmd,4,0);
577	msgcmd=0;
578      }
579     bufwrite(msg,strlen(msg),flush||endoread());
580     flush=0;
581   }
582}
583
584void flushoverread P(())		 /* pass upwards the extra LMTP data */
585{ int i;
586  while(overlen)
587   { if(0>(i=rwrite(ctopfd,overread,overlen)))
588	return;				       /* there's nothing to be done */
589     overlen-=i;
590     overread+=i;
591   }
592}
593
594void freeoverread P(())			    /* blow away the extra LMTP data */
595{ if(overread)
596   { bbzero(overread,overlen);
597     free(overread);
598     overread=0;
599   }
600}
601
602#define X(str)	{str,STRLEN(str)}
603static struct{const char*mess;int len;}ret2LMTP[]=
604{ X("500 5.0.0 usage error\r\n"),				    /* USAGE */
605  X("501 5.6.0 data error\r\n"),				  /* DATAERR */
606  X("550 5.3.0 input missing\r\n"),				  /* NOINPUT */
607  X("550 5.1.1 no such user\r\n"),				   /* NOUSER */
608  X("550 5.1.2 no such host\r\n"),				   /* NOHOST */
609  X("554 5.0.0 something didn't work\r\n"),		      /* UNAVAILABLE */
610  X("554 5.3.0 internal software error\r\n"),			 /* SOFTWARE */
611  X("451 4.0.0 OS error\r\n"),					    /* OSERR */
612  X("554 5.3.5 system file error\r\n"),				   /* OSFILE */
613  X("550 5.0.0 output error\r\n"),				/* CANTCREAT */
614  X("451 4.0.0 I/O error\r\n"),					    /* IOERR */
615  X("450 4.0.0 deferred\r\n"),					 /* TEMPFAIL */
616  X("554 5.5.0 protocol error\r\n"),				 /* PROTOCOL */
617  X("550 5.0.0 insufficient permission\r\n"),			   /* NOPERM */
618  X("554 5.3.5 configuration error\r\n"),			   /* CONFIG */
619};
620#undef X
621
622void lmtpresponse(retcode)int retcode;
623{ const char*message;int len;
624  if(!retcode)
625     message=protogood,len=STRLEN(protogood);
626  else
627   { if(retcode<0)
628	retcode=EX_SOFTWARE;
629     if(0>(retcode-=EX__BASE)||retcode>=(sizeof ret2LMTP/sizeof ret2LMTP[0]))
630	retcode=EX_UNAVAILABLE-EX__BASE;
631     message=ret2LMTP[retcode].mess;len=ret2LMTP[retcode].len;
632   }
633  if(len!=rwrite(savstdout,message,len))
634     exit(EX_OSERR);
635}
636
637#define IS_READERR	(-1)
638#define IS_NORMAL	0
639#define IS_CR		1
640#define IS_CRBOL	2
641#define IS_CRDOT	3
642#define IS_DOTCR	4
643#define IS_NLBOL	5
644#define IS_NLDOT	6
645
646#define EXIT_LOOP(s)	{state=(s);goto loop_exit;}
647
648static char*lmtp_read_crnl(char*p,long left,void*statep)
649{ int got,state= *(int*)statep;
650  register char*in,*q,*last;
651  do
652   { if(0>=(got=readL(p,left)))					/* read mail */
653      { state=IS_READERR;
654	return p;
655      }
656     last=(in=p)+got;
657     /*
658      * A state machine to read LMTP data.  If 'nliseol' isn't set,
659      * then \r\n is the end-o'-line string, and \n is only special
660      * in it.	\r's are stripped from \r\n, but are otherwise preserved.
661      */
662     switch(state)
663      { case IS_CR:   goto is_cr;
664	case IS_CRBOL:goto is_crbol;
665	case IS_CRDOT:goto is_crdot;
666	case IS_DOTCR:goto is_dotcr;
667	case IS_NORMAL:break;
668	case IS_NLBOL:case IS_NLDOT:case IS_READERR:
669	   exit(EX_SOFTWARE);
670      }
671     while(in<last)
672	if((q=memchr(in,'\r',last-in))?q>in:!!(q=last))
673	 { if(in!=p)
674	      memmove(p,in,q-in);
675	   p+=q-in;in=q;
676	 }
677	else							       /* CR */
678	 {
679found_cr:  *p++= *in++;				   /* tenatively save the \r */
680	   if(in==last)
681	      EXIT_LOOP(IS_CR)
682is_cr:	   if(*in!='\n')
683	      continue;
684	   p[-1]= *in++;				 /* overwrite the \r */
685	   if(in==last)						     /* CRLF */
686	      EXIT_LOOP(IS_CRBOL)
687is_crbol:  if(*in=='\r')					 /* CRLF CR? */
688	      goto found_cr;
689	   if(*in!='.')
690	    { *p++= *in++;
691	      continue;
692	    }
693	   if(++in==last)					 /* CRLF "." */
694	      EXIT_LOOP(IS_CRDOT)
695is_crdot:  if((*p++= *in++)!='\r')
696	      continue;
697	   if(in==last)					      /* CRLF "." CR */
698	      EXIT_LOOP(IS_DOTCR)
699is_dotcr:  if(*in=='\n')				    /* CRLF "." CRLF */
700	    { p--;				   /* remove the trailing \r */
701	      if((overlen=last-++in)>0)		 /* should never be negative */
702		 tmemmove(overread=malloc(overlen),in,overlen);
703	      return p;
704	    }
705	 }
706     state=IS_NORMAL;		 /* we must have fallen out because in==last */
707loop_exit:
708     got-=in-p;				     /* correct for what disappeared */
709   }
710  while(left-=got);				/* change listed buffer size */
711  *(long*)statep=state;					       /* save state */
712  return 0;
713}
714
715static char*lmtp_read_nl(char*p,long left,void*statep)
716{ int got,state= *(int*)statep;
717  register char*in,*q,*last;
718  do
719   { if(0>=(got=readL(p,left)))					/* read mail */
720      { state=IS_READERR;
721	return p;
722      }
723     last=(in=p)+got;
724     /*
725      * A state machine to read LMTP data.  \n is the end-o'-line
726      * character and \r is not special at all.
727      */
728     switch(state)
729      { case IS_CR:case IS_CRBOL:case IS_CRDOT:case IS_DOTCR:
730	case IS_READERR:
731	   exit(EX_SOFTWARE);
732	case IS_NLBOL:goto is_nlbol;
733	case IS_NLDOT:goto is_nldot;
734	case IS_NORMAL:break;
735      }
736     while(in<last)
737	if((q=memchr(in,'\n',last-in))?q>in:!!(q=last))
738	 { if(in!=p)
739	      memmove(p,in,q-in);
740	   p+=q-in;in=q;
741	 }
742	else							       /* LF */
743	 { do
744	    { *p++= *in++;
745is_nlbol:     ;
746	    }
747	   while(in<last&&*in=='\n');
748	   if(in==last)
749	      EXIT_LOOP(IS_NLBOL)
750	   if(*in!='.')
751	    { *p++= *in++;
752	      continue;
753	    }
754	   if(++in==last)					   /* LF "." */
755	      EXIT_LOOP(IS_NLDOT)
756is_nldot:  if(*in=='\n')					/* LF "." LF */
757	    { if((overlen=last-++in)>0)		 /* should never be negative */
758		 tmemmove(overread=malloc(overlen),in,overlen);
759	      return p;
760	    }
761	   *p++= *in++;
762	 }
763     state=IS_NORMAL;		 /* we must have fallen out because in==last */
764loop_exit:
765     got-=in-p;				     /* correct for what disappeared */
766   }
767  while(left-=got);				/* change listed buffer size */
768  *(long*)statep=state;					       /* save state */
769  return 0;
770}
771
772static int lreaddyn()
773{ int state=nliseol?IS_NLBOL:IS_CRBOL;
774  read2blk(&themail,&filled,nliseol?&lmtp_read_nl:&lmtp_read_crnl,
775   (cleanup_func_type*)0,&state);
776  return state!=IS_READERR;
777}
778#else
779int lmtp_dummy_var;		      /* to prevent insanity in some linkers */
780#endif
781