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
9 static /*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 
27 lhlo		=> print stuff
28 mail from:	=> fork
29 		   Generate From_ line (just body type)
30 rcpt to:	=> deduce pass entry, push it somewhere
31 data		=> process data till end, if we run out of mem, give 552
32 			(or 452?) response
33 bdat		=> allocate requested size buffer & read it in, or 552/452
34 			reponse.
35 vrfy		=> just check the user
36 end of data	=> fork, perform deliveries, giving error codes as you go.
37 rset		=> 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 
50 static int lreaddyn P((void));
51 
52 int childserverpid;
53 
54 static ctopfd;
55 static char*overread;
56 static size_t overlen;
57 
58 static int nliseol;			 /* is a plain \n the EOL delimiter? */
59 static char*bufcur;
60 static const char
61  lhlomsg[]=
62 "250-localhost\r\n\
63 250-SIZE\r\n\
64 250-8BITMIME\r\n\
65 250-PIPELINING\r\n\
66 250-CHUNKING\r\n\
67 250 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 
bufwrite(buffer,len,flush)74 static void bufwrite(buffer,len,flush)
75 const 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 
unexpect(str)88 static 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 
slurpnumber(void)103 static 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 */
slurpaddress(void)121 static 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 ';':
161 syntax_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 */
extractaddress(path)175 static 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 	 }
193 found:
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 /*
217 linebuf 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) */
lmtp(lrout,invoker)237 struct auth_identity **lmtp(lrout,invoker)
238 struct 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;
316 unknown_param:	       msg="504 5.5.4 unknown MAIL parameter or bad value\r\n";
317 		       goto message;
318 		  }
319 jumpin:		 do c=getL();
320 		 while(c==' ');
321 		}
322 	      while(c!='\n');
323 jumpout:      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 	    }
438 deliver:   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 		  {
470 bad_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;
550 quit:	   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;
566 unknown_command:
567 	case '\n':
568 	   msg="500 5.5.1 Unknown command given\r\n";msgcmd=0;
569 	   flush=1;
570 	   break;
571       }
572 message:
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 
flushoverread()584 void 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 
freeoverread()594 void 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)}
603 static 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 
lmtpresponse(retcode)622 void 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 
lmtp_read_crnl(char * p,long left,void * statep)648 static 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 	 {
679 found_cr:  *p++= *in++;				   /* tenatively save the \r */
680 	   if(in==last)
681 	      EXIT_LOOP(IS_CR)
682 is_cr:	   if(*in!='\n')
683 	      continue;
684 	   p[-1]= *in++;				 /* overwrite the \r */
685 	   if(in==last)						     /* CRLF */
686 	      EXIT_LOOP(IS_CRBOL)
687 is_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)
695 is_crdot:  if((*p++= *in++)!='\r')
696 	      continue;
697 	   if(in==last)					      /* CRLF "." CR */
698 	      EXIT_LOOP(IS_DOTCR)
699 is_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 */
707 loop_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 
lmtp_read_nl(char * p,long left,void * statep)715 static 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++;
745 is_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)
756 is_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 */
764 loop_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 
lreaddyn()772 static 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
779 int lmtp_dummy_var;		      /* to prevent insanity in some linkers */
780 #endif
781