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