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