#ifdef RCS
static char rcsid[]=
"$Id: lmtp.c,v 1.13 2001/06/28 21:44:28 guenther Exp $"
#endif
#include "procmail.h"
#ifdef LMTP
#include "sublib.h"
#include "robust.h"
#include "misc.h"
#include "shell.h"
#include "authenticate.h"
#include "cstdio.h"
#include "mailfold.h"
#include "memblk.h"
#include "from.h"
#include "lmtp.h"
#define INITIAL_RCPTS 10
#define INCR_RCPTS 20
static int lreaddyn P((void));
int childserverpid;
static ctopfd;
static char*overread;
static size_t overlen;
static int nliseol;
static char*bufcur;
static const char
lhlomsg[]=
"250-localhost\r\n\
250-SIZE\r\n\
250-8BITMIME\r\n\
250-PIPELINING\r\n\
250-CHUNKING\r\n\
250 ENHANCEDSTATUSCODES\r\n",
nomemmsg[]="452 4.3.0 insufficient system storage\r\n",
quitmsg[]="221 2.3.0 Goodbye!\r\n",
baduser[]="550 5.1.1 mailbox unknown\r\n",
protogood[]="250 2.5.0 command succeeded\r\n",
protobad[]="503 5.5.1 command unexpected\r\n";
static void bufwrite(buffer,len,flush)
const char*buffer;int len;int flush;
{ int already;
if((already=bufcur-buf2)+len>linebuf||flush)
{ if(already&&already!=rwrite(savstdout,bufcur=buf2,already)||
len&&len!=rwrite(savstdout,buffer,len))
exit(EX_OSERR);
}
else
bufcur=(char*)tmemmove(bufcur,buffer,len)+len;
}
#define bufinit bufcur=buf2
#define skiptoeol do c=getL(); while(c!='\n');
static int unexpect(str)const char*str;
{ char c;
while(*str!='\0')
{ if((c=getL())-'a'<='z'-'a'&&c>='a')c-='a'-'A';
if(c!= *str)
{ if(c!='\n')
skiptoeol;
return 1;
}
str++;
}
return 0;
}
#define NOTcommand(command,offset) unexpect((msgcmd=command)+offset)
static long slurpnumber P((void))
{ int c;long total=0;
while((c=getL())-'0'>=0&&c-'0'<10)
{ total*=10;
total+=c-'0';
}
if(c!=' '&&c!='\n'&&c!='\r')
{ skiptoeol;
return -1;
}
if(c=='\n')
{ ungetb(c);
}
return total;
}
static char *slurpaddress P((void))
{ char*p=buf,c,*const last=buf+linebuf-1;
do{*p=getL();}while(*p==' ');
if(*p!='<')
{ if(*p!='\n')
skiptoeol;
return 0;
}
while(++p<last)
switch(*p=getL())
{ case '\\':
if(++p==last)
goto syntax_error;
*p++=getL();
continue;
case '"':
while(++p<last)
switch(*p=getL())
{ case '\\':
if(++p==last)
goto syntax_error;
*p=getL();
continue;
case '"':
break;
case '\r':
goto syntax_error;
case '\n':
return 0;
default:
continue;
}
continue;
case '\00':case '\01':case '\02':case '\03':case '\04':
case '\05':case '\06':case '\07':case '\10':case '\11':
case '\13':case '\14':case '\15':case '\16':
case '\17':case '\20':case '\21':case '\22':case '\23':
case '\24':case '\25':case '\26':case '\27':case '\30':
case '\31':case '\32':case '\33':case '\34':case '\35':
case '\36':case '\37':case '<':case '(':case ')':case ';':
syntax_error:
skiptoeol;
case '\n':
return 0;
case '>':
*p='\0';
return tstrdup(buf);
default:
break;
}
goto syntax_error;
}
static char *extractaddress(path)char *path;
{ char *p=path+1,*q=path;
if(*p=='@')
while(1)
switch(*++p)
{ case ':':p++;
goto found;
case '\\':p++;
break;
case '"':
while(*++p!='"')
if(*p=='\\')
p++;
break;
case '>':case '\0':
free(path);
return 0;
}
found:
while(1)
{ switch(*p)
{ case '\\':
p++;
break;
case '"':
while(*++p!='"')
if(*p=='\\')
*q++= *++p;
else
*q++= *p;
p++;
continue;
case '\0':case '>':
case '@':
*q='\0';
return path;
}
*q++= *p++;
}
}
#define S_START 0
#define S_MAIL 1
#define S_RCPT 2
#define S_BDAT 3
struct auth_identity **lmtp(lrout,invoker)
struct auth_identity***lrout;char*invoker;
{ static const char cLHLO[]="LHLO ",cMAIL[]="MAIL FROM:",cRCPT[]="RCPT TO:",
cDATA[]="DATA",cBDAT[]="BDAT",cRSET[]="RSET",cVRFY[]="VRFY ",cQUIT[]="QUIT",
cNOOP[]="NOOP";
const char*msg,*msgcmd;int flush=0,c,lmtp_state=S_START;long size=0;
auth_identity**rcpts,**lastrcpt,**currcpt;
pushfd(STDIN);overread=0;overlen=0;nliseol=1;
bufinit;ctopfd=-1;
currcpt=rcpts=malloc(INITIAL_RCPTS*sizeof*rcpts);
lastrcpt=INITIAL_RCPTS+currcpt;
bufwrite("220 ",4,0);bufwrite(procmailn,strlen(procmailn),0);
bufwrite(Version,strchr(Version,'\n')-Version,0);
bufwrite(" LMTP\r\n",7,1);
while(1)
{ do{c=getL();}while(c==' ');
switch(c)
{ case 'l': case 'L':
if(NOTcommand(cLHLO,1))
goto unknown_command;
;{ int sawcrnl=0;
while((c=getL())!='\n')
if(c=='\r')
{ c=getL();
if(c=='\n')
{ sawcrnl=1;
break;
}
}
flush=1;
if(lmtp_state!=S_START)
{ msg=protobad;
goto message;
}
else
{ lmtp_state=S_MAIL;
msg=lhlomsg;msgcmd=0;
if(sawcrnl)
nliseol=0;
}
}
goto message;
case 'm': case 'M':
if(NOTcommand(cMAIL,1))
goto unknown_command;
;{ int pipefds[2];char*from;
if(lmtp_state!=S_MAIL)
{ skiptoeol;msg=protobad;
goto message;
}
if(!(from=slurpaddress()))
{ msg="553 5.1.7 Unable to parse MAIL address\r\n";
goto message;
}
size=0;
goto jumpin;
do
{ switch(c)
{ case 's':case 'S':
if(unexpect("IZE="))
goto unknown_param;
size=slurpnumber();
if(size<0)
goto unknown_param;
break;
case 'b':case 'B':
if(unexpect("ODY="))
goto unknown_param;
while((c=getL())!='\r')
switch(c)
{ case ' ':goto jumpin;
case '\n':goto jumpout;
}
case '\r':
if((c=getL())=='\n')
continue;
default:
skiptoeol;
unknown_param: msg="504 5.5.4 unknown MAIL parameter or bad value\r\n";
goto message;
}
jumpin: do c=getL();
while(c==' ');
}
while(c!='\n');
jumpout: rpipe(pipefds);
if(!(childserverpid=sfork()))
{ char status=0;
rclose(pipefds[0]);
ctopfd=pipefds[1];
bufwrite(0,0,1);
makeFrom(from+1,invoker);
free(from);
if(size&&!resizeblock(&themail,size+=filled+3,1))
{ status=1;
bufwrite(nomemmsg,STRLEN(nomemmsg),1);
}
if(rwrite(pipefds[1],&status,sizeof(status))!=sizeof(status))
exit(EX_OSERR);
if(status)
exit(0);
lmtp_state=S_RCPT;
msg=protogood;
goto message;
}
rclose(pipefds[1]);
if(!forkerr(childserverpid,buf))
{ char status=1;
rread(pipefds[0],&status,sizeof(status));
if(!status)
{ pushfd(pipefds[0]);
lmtp_state=S_MAIL;
bufinit;
}
continue;
}
rclose(pipefds[0]);
msg="421 4.3.2 unable to fork for MAIL\r\n";
goto message;
}
case 'r': case 'R':
if((c=getL())=='s'||c=='S')
{ if(NOTcommand(cRSET,2))
goto unknown_command;
skiptoeol;
if(lmtp_state!=S_START)
lmtp_state=S_MAIL;
msg=protogood;
goto message;
}
if((c!='c'&&c!='C')||NOTcommand(cRCPT,2))
goto unknown_command;
if(lmtp_state!=S_RCPT)
{ skiptoeol;
msg=protobad;
goto message;
}
if(currcpt==lastrcpt)
{ int num=lastrcpt-rcpts;
rcpts=realloc(rcpts,(num+INCR_RCPTS)*sizeof*rcpts);
currcpt=rcpts+num;lastrcpt=currcpt+INCR_RCPTS;
}
;{ char *path,*mailbox;auth_identity*temp;
if(!(path=slurpaddress())||!(mailbox=extractaddress(path)))
{ msg="550 5.1.3 address syntax error\r\n";
goto message;
}
skiptoeol;
if(!(temp=auth_finduser(mailbox,0)))
{ msg="550 5.1.1 mailbox unknown\r\n";
free(path);
goto message;
}
auth_copyid(*currcpt=auth_newid(),temp);
free(path);
currcpt++;
msg="250 2.1.5 ok\r\n";
goto message;
}
case 'd': case 'D':
flush=1;
if(NOTcommand(cDATA,1))
goto unknown_command;
skiptoeol;
if(lmtp_state!=S_RCPT)
{ msg=protobad;
goto message;
}
if(currcpt==rcpts)
{ msg="554 5.5.1 to whom?\r\n";
goto message;
}
msg="354 Enter DATA terminated with a solo \".\"\r\n";
bufwrite(msg,strlen(msg),1);
if(!(lreaddyn()))
{
bufwrite(nomemmsg,STRLEN(nomemmsg),1);
goto quit;
}
deliver: readmail(2,0L);
lastrcpt=rcpts;
rcpts=realloc(rcpts,(currcpt-rcpts)*sizeof*rcpts);
*lrout=(currcpt-lastrcpt)+rcpts;
return rcpts;
case 'b': case 'B':
if(NOTcommand(cBDAT,1))
goto unknown_command;
if((c=getL())!=' ')
{ if(c!='\n')
skiptoeol;
msg="504 5.5.4 octets count missing\r\n";
goto message;
}
if(lmtp_state<S_RCPT)
{ msg=protobad;
goto message;
}
if(currcpt==rcpts)
{ msg="554 5.5.1 to whom?\r\n";
goto message;
}
;{ int last=0;
long length=slurpnumber();
if(length<0)
{ msg="555 5.5.4 octet count bad\r\n";
goto message;
}
do{c=getL();}while(c==' ');
if(c=='l'||c=='L')
{ if(unexpect("AST"))
{
bad_bdat_param: msg="504 5.5.4 parameter unknown\r\n";
goto message;
}
last=1;
c=getL();
}
if(!nliseol&&c=='\r')
c=getL();
if(c!='\n')
{ skiptoeol;
goto bad_bdat_param;
}
if(filled+length>size)
{ if(!resizeblock(&themail,size=filled+length+BLKSIZ,1))
{ int i;
while(length>linebuf)
{ i=readLe(buf,linebuf);
if(i<0)
goto quit;
length-=i;
}
if(length&&0>readLe(buf,length))
goto quit;
lmtp_state=S_MAIL;
msg=nomemmsg;
flush=1;
goto message;
}
}
while(length>0)
{ int i=readLe(themail.p+filled,length);
if(!i)
exit(EX_NOINPUT);
else if(i<0)
goto quit;
length-=i;
filled+=i;
}
if(last)
{ if(!nliseol)
{ char*in,*out,*q,*last;
last=(in=out=themail.p)+filled;
while(in<last)
if((q=memchr(in,'\r',last-in))?q>in:!!(q=last))
{ if(in!=out)
memmove(out,in,q-in);
out+=q-in;in=q;
}
else if(++in==last||*in!='\n')
*out++='\r';
resizeblock(&themail,(filled-=in-out)+1,1);
}
goto deliver;
}
msg=protogood;
goto message;
}
case 'v': case 'V':
if(NOTcommand(cVRFY,1))
goto unknown_command;
flush=1;
;{ char *path,*mailbox;
auth_identity *temp;
if(!(path=slurpaddress())||!(mailbox=extractaddress(path)))
{ msg="501 5.1.3 address syntax error\r\n";
goto message;
}
skiptoeol;
if(!(temp=auth_finduser(mailbox,0)))
{ msg="550 5.1.1 user unknown\r\n";
free(path);
goto message;
}
free(path);
msg="252 2.5.0 successful\r\n";
goto message;
}
case 'q': case 'Q':
if(NOTcommand(cQUIT,1))
goto unknown_command;
quit: if(ctopfd>=0)
{ rwrite(ctopfd,cQUIT,STRLEN(cQUIT));
rclose(ctopfd);
}
else
bufwrite(quitmsg,STRLEN(quitmsg),1);
exit(0);
case 'n': case 'N':
if(NOTcommand(cNOOP,1))
goto unknown_command;
skiptoeol;
flush=1;
msg="200 2.0.0 ? Nope\r\n";
goto message;
default:
skiptoeol;
unknown_command:
case '\n':
msg="500 5.5.1 Unknown command given\r\n";msgcmd=0;
flush=1;
break;
}
message:
bufwrite(msg,10,0);msg+=10;
if(msgcmd)
{ msg--;
bufwrite(msgcmd,4,0);
msgcmd=0;
}
bufwrite(msg,strlen(msg),flush||endoread());
flush=0;
}
}
void flushoverread P(())
{ int i;
while(overlen)
{ if(0>(i=rwrite(ctopfd,overread,overlen)))
return;
overlen-=i;
overread+=i;
}
}
void freeoverread P(())
{ if(overread)
{ bbzero(overread,overlen);
free(overread);
overread=0;
}
}
#define X(str) {str,STRLEN(str)}
static struct{const char*mess;int len;}ret2LMTP[]=
{ X("500 5.0.0 usage error\r\n"),
X("501 5.6.0 data error\r\n"),
X("550 5.3.0 input missing\r\n"),
X("550 5.1.1 no such user\r\n"),
X("550 5.1.2 no such host\r\n"),
X("554 5.0.0 something didn't work\r\n"),
X("554 5.3.0 internal software error\r\n"),
X("451 4.0.0 OS error\r\n"),
X("554 5.3.5 system file error\r\n"),
X("550 5.0.0 output error\r\n"),
X("451 4.0.0 I/O error\r\n"),
X("450 4.0.0 deferred\r\n"),
X("554 5.5.0 protocol error\r\n"),
X("550 5.0.0 insufficient permission\r\n"),
X("554 5.3.5 configuration error\r\n"),
};
#undef X
void lmtpresponse(retcode)int retcode;
{ const char*message;int len;
if(!retcode)
message=protogood,len=STRLEN(protogood);
else
{ if(retcode<0)
retcode=EX_SOFTWARE;
if(0>(retcode-=EX__BASE)||retcode>=(sizeof ret2LMTP/sizeof ret2LMTP[0]))
retcode=EX_UNAVAILABLE-EX__BASE;
message=ret2LMTP[retcode].mess;len=ret2LMTP[retcode].len;
}
if(len!=rwrite(savstdout,message,len))
exit(EX_OSERR);
}
#define IS_READERR (-1)
#define IS_NORMAL 0
#define IS_CR 1
#define IS_CRBOL 2
#define IS_CRDOT 3
#define IS_DOTCR 4
#define IS_NLBOL 5
#define IS_NLDOT 6
#define EXIT_LOOP(s) {state=(s);goto loop_exit;}
static char*lmtp_read_crnl(char*p,long left,void*statep)
{ int got,state= *(int*)statep;
register char*in,*q,*last;
do
{ if(0>=(got=readL(p,left)))
{ state=IS_READERR;
return p;
}
last=(in=p)+got;
switch(state)
{ case IS_CR: goto is_cr;
case IS_CRBOL:goto is_crbol;
case IS_CRDOT:goto is_crdot;
case IS_DOTCR:goto is_dotcr;
case IS_NORMAL:break;
case IS_NLBOL:case IS_NLDOT:case IS_READERR:
exit(EX_SOFTWARE);
}
while(in<last)
if((q=memchr(in,'\r',last-in))?q>in:!!(q=last))
{ if(in!=p)
memmove(p,in,q-in);
p+=q-in;in=q;
}
else
{
found_cr: *p++= *in++;
if(in==last)
EXIT_LOOP(IS_CR)
is_cr: if(*in!='\n')
continue;
p[-1]= *in++;
if(in==last)
EXIT_LOOP(IS_CRBOL)
is_crbol: if(*in=='\r')
goto found_cr;
if(*in!='.')
{ *p++= *in++;
continue;
}
if(++in==last)
EXIT_LOOP(IS_CRDOT)
is_crdot: if((*p++= *in++)!='\r')
continue;
if(in==last)
EXIT_LOOP(IS_DOTCR)
is_dotcr: if(*in=='\n')
{ p--;
if((overlen=last-++in)>0)
tmemmove(overread=malloc(overlen),in,overlen);
return p;
}
}
state=IS_NORMAL;
loop_exit:
got-=in-p;
}
while(left-=got);
*(long*)statep=state;
return 0;
}
static char*lmtp_read_nl(char*p,long left,void*statep)
{ int got,state= *(int*)statep;
register char*in,*q,*last;
do
{ if(0>=(got=readL(p,left)))
{ state=IS_READERR;
return p;
}
last=(in=p)+got;
switch(state)
{ case IS_CR:case IS_CRBOL:case IS_CRDOT:case IS_DOTCR:
case IS_READERR:
exit(EX_SOFTWARE);
case IS_NLBOL:goto is_nlbol;
case IS_NLDOT:goto is_nldot;
case IS_NORMAL:break;
}
while(in<last)
if((q=memchr(in,'\n',last-in))?q>in:!!(q=last))
{ if(in!=p)
memmove(p,in,q-in);
p+=q-in;in=q;
}
else
{ do
{ *p++= *in++;
is_nlbol: ;
}
while(in<last&&*in=='\n');
if(in==last)
EXIT_LOOP(IS_NLBOL)
if(*in!='.')
{ *p++= *in++;
continue;
}
if(++in==last)
EXIT_LOOP(IS_NLDOT)
is_nldot: if(*in=='\n')
{ if((overlen=last-++in)>0)
tmemmove(overread=malloc(overlen),in,overlen);
return p;
}
*p++= *in++;
}
state=IS_NORMAL;
loop_exit:
got-=in-p;
}
while(left-=got);
*(long*)statep=state;
return 0;
}
static int lreaddyn()
{ int state=nliseol?IS_NLBOL:IS_CRBOL;
read2blk(&themail,&filled,nliseol?&lmtp_read_nl:&lmtp_read_crnl,
(cleanup_func_type*)0,&state);
return state!=IS_READERR;
}
#else
int lmtp_dummy_var;
#endif