/* SMTP Server state machine - see RFC 821 * Very simple implementation; no forwarding allowed * (who wants to re-create "sendmail" ??) */ #include #include "machdep.h" #include "mbuf.h" #include "netuser.h" #include "timer.h" #include "tcp.h" #include "smtp.h" #ifdef LATTICE #define tmpfile(x) tmpnam("testingxxxxxxxxxx") #endif /* Command table */ static char *commands[] = { "helo", #define HELO_CMD 0 "noop", #define NOOP_CMD 1 "mail from:", #define MAIL_CMD 2 "quit", #define QUIT_CMD 3 "rcpt to:", #define RCPT_CMD 4 "help", #define HELP_CMD 5 "data", #define DATA_CMD 6 "rset", #define RSET_CMD 7 NULLCHAR }; /* Reply messages */ static char help[] = "214-Commands:\r\n214-HELO NOOP MAIL QUIT RCPT HELP DATA RSET\r\n214 End\r\n"; static char banner[] = "220 %s SMTP Ready\r\n"; static char closing[] = "221 Closing\r\n"; static char ok[] = "250 Ok\r\n"; static char reset[] = "250 Reset state\r\n"; static char sent[] = "250 Sent\r\n"; static char ourname[] = "250 %s, Share and Enjoy!\r\n"; static char enter[] = "354 Enter mail, end with .\r\n"; static char ioerr[] = "452 Temp file write error\r\n"; static char mboxerr[] = "452 Mailbox %s write error\r\n"; static char badcmd[] = "500 Command unrecognized\r\n"; static char syntax[] = "501 Syntax error\r\n"; static char needrcpt[] = "503 Need RCPT (recipient)\r\n"; static char badname[] = "550 Can't open mailbox for %s\r\n"; static struct tcb *smtp_tcb; /* Start up SMTP receiver service */ smtp_start(argc,argv) int argc; char *argv[]; { struct socket lsocket; void r_mail(),s_mail(); lsocket.address = ip_addr; if(argc < 2) lsocket.port = SMTP_PORT; else lsocket.port = atoi(argv[1]); smtp_tcb = open_tcp(&lsocket,NULLSOCK, TCP_PASSIVE,0,r_mail,NULLVFP,s_mail,0,(int *)NULL); } /* Shutdown SMTP service (existing connections are allowed to finish) */ smtp_stop() { if(smtp_tcb != NULLTCB) close_tcp(smtp_tcb); } /* SMTP connection state change upcall handler */ static void s_mail(tcb,old,new) struct tcb *tcb; char old,new; { struct mail *mp,*mail_create(); switch(new){ #ifdef QUICKSTART case SYN_RECEIVED: #else case ESTABLISHED: #endif if((mp = mail_create(tcb)) == NULLMAIL){ close_tcp(tcb); break; } tprintf(mp->tcb,banner,hostname); log(tcb,"open SMTP"); break; case CLOSE_WAIT: mp = (struct mail *)tcb->user; mail_delete(mp); close_tcp(tcb); break; case CLOSED: log(tcb,"close SMTP"); del_tcp(tcb); /* Check if server is being shut down */ if(tcb == smtp_tcb) smtp_tcb = NULLTCB; break; } } /* SMTP receiver upcall handler */ static void r_mail(tcb,cnt) struct tcb *tcb; int16 cnt; { register struct mail *mp; char *index(),*inet_ntoa(),c; struct mbuf *bp; void docommand(),deliver(),doline(); if((mp = (struct mail *)tcb->user) == NULLMAIL){ /* Unknown session */ close_tcp(tcb); return; } recv_tcp(tcb,&bp,cnt); /* Assemble an input line in the session buffer. * Return if incomplete */ while(pullup(&bp,&c,1) == 1){ switch(c){ case '\r': /* Strip cr's */ continue; case '\n': /* Complete line; process it */ mp->buf[mp->cnt] = '\0'; doline(mp); break; default: /* Assemble line */ mp->buf[mp->cnt++] = c; break; } } } /* Process a line read on an SMTP connection (any state) */ static void doline(mp) register struct mail *mp; { switch(mp->state){ case COMMAND_STATE: docommand(mp); break; case DATA_STATE: tcp_output(mp->tcb); /* Send ACK; disk I/O is slow */ if(mp->buf[0] == '.' && strlen(mp->buf) == 1){ fprintf(mp->data,"\n"); /* Leave a blank line between msgs */ mp->state = COMMAND_STATE; deliver(mp); /* Also sends appropriate response */ break; } /* Append to data file */ if(fprintf(mp->data,"%s\n",mp->buf) < 0){ mp->state = COMMAND_STATE; tprintf(mp->tcb,ioerr); } break; } mp->cnt = 0; } /* Create control block, initialize */ static struct mail * mail_create(tcb) register struct tcb *tcb; { register struct mail *mp; char *calloc(); if((mp = (struct mail *)calloc(1,sizeof (struct mail))) == NULLMAIL){ return NULLMAIL; } mp->tcb = tcb; /* Downward pointer */ tcb->user = (int *)mp; /* Upward pointer */ return mp; } /* Free resources, delete control block */ static mail_delete(mp) register struct mail *mp; { register struct addr *ap,*ap1; if(mp->system != NULLCHAR) free(mp->system); for(ap = mp->to;ap != NULLADDR;ap = ap1){ if(ap->val != NULLCHAR) free(ap->val); ap1 = ap->next; free((char *)ap); } if(mp->data != NULLFILE) fclose(mp->data); free((char *)mp); } /* Parse and execute mail commands */ static void docommand(mp) register struct mail *mp; { char mailbox[50]; char *cmd,*arg,*cp,*cp1,**cmdp; char *index(),*malloc(),*getname(); struct tcb *tcb; struct addr *ap; #ifdef LATTICE FILE *fp; #else FILE *tmpfile(),*fp; #endif #ifdef DATE long t; char *ctime(); #endif cmd = mp->buf; if(mp->cnt < 4){ /* Can't be a legal SMTP command */ tprintf(mp->tcb,badcmd); return; } cmd = mp->buf; /* Translate entire buffer to lower case */ for(cp = cmd;*cp != '\0';cp++) *cp = tolower(*cp); /* Find command in table; if not present, return syntax error */ for(cmdp = commands;*cmdp != NULLCHAR;cmdp++) if(strncmp(*cmdp,cmd,strlen(*cmdp)) == 0) break; if(*cmdp == NULLCHAR){ tprintf(mp->tcb,badcmd); return; } arg = &cmd[strlen(*cmdp)]; /* Skip spaces after command */ while(*arg == ' ') *arg++; /* Execute specific command */ switch(cmdp-commands){ case HELO_CMD: if((mp->system = malloc((unsigned)strlen(arg)+1)) == NULLCHAR){ /* If the system is out of memory, just close */ close_tcp(mp->tcb); mail_delete(mp); break; } else { strcpy(mp->system,arg); tprintf(mp->tcb,ourname,hostname); } break; case NOOP_CMD: case MAIL_CMD: /* Rather useless */ tprintf(mp->tcb,ok); break; case QUIT_CMD: tprintf(mp->tcb,closing); close_tcp(mp->tcb); mail_delete(mp); break; case RCPT_CMD: /* Specify recipient */ if((cp = getname(arg)) == NULLCHAR){ tprintf(mp->tcb,syntax); break; } /* Strip the @host part; all names must be local */ if((cp1 = index(cp,'@')) != NULLCHAR) *cp1 = '\0'; /* Check to see if we can open the mailbox */ sprintf(mailbox,MAILSPOOL,cp); #ifdef AMIGA /* probably could use AMIGA's Lock()/Unlock(), but that seems like overkill */ if((fp = fopen(mailbox,"r")) == NULL){ #else if((fp = fopen(mailbox,"a+")) == NULL){ #endif tprintf(mp->tcb,badname,cp); break; } fclose(fp); /* Allocate an entry on the recipient list. This * assembles the list backwards, but what the heck. */ if((ap = (struct addr *)malloc(sizeof(struct addr))) == NULLADDR){ close_tcp(mp->tcb); mail_delete(mp); break; } if((ap->val = malloc((unsigned)strlen(mailbox)+1)) == NULLCHAR){ free((char *)ap); close_tcp(mp->tcb); mail_delete(mp); break; } strcpy(ap->val,mailbox); ap->next = mp->to; mp->to = ap; tprintf(mp->tcb,ok); break; case HELP_CMD: tprintf(mp->tcb,help); break; case DATA_CMD: if(mp->to == NULLADDR){ tprintf(mp->tcb,needrcpt); break; } tcp_output(mp->tcb); /* Send ACK; disk I/O is slow */ if((mp->data = tmpfile()) == NULLFILE){ tprintf(mp->tcb,ioerr); break; } #ifdef DATE /* Add timestamp; ctime adds newline */ time(&t); if(mp->system != NULLCHAR) fprintf(mp->data, "Received: from %s by %s\n\twith SMTP ; %s", mp->system, hostname, ctime(&t)); else fprintf(mp->data,"Received: %s\n",ctime(&t)); #endif if(ferror(mp->data)){ tprintf(mp->tcb,ioerr); } else { mp->state = DATA_STATE; tprintf(mp->tcb,enter); } break; case RSET_CMD: tcb = mp->tcb; mail_delete(mp); if((mp = mail_create(tcb)) == NULLMAIL){ close_tcp(tcb); } else { mp->state = COMMAND_STATE; tprintf(mp->tcb,reset); } break; } } /* Given a string of the form , extract the part inside the * brackets and return a pointer to it. */ static char * getname(cp) register char *cp; { char *cp1; char *index(),*strncpy(); if((cp = index(cp,'<')) == NULLCHAR){ return NULLCHAR; } cp++; /* cp -> first char of name */ if((cp1 = index(cp,'>')) == NULLCHAR){ return NULLCHAR; } *cp1 = '\0'; return cp; } /* Deliver mail to the appropriate mail boxes and delete temp file */ static void deliver(mp) register struct mail *mp; { char *index(); int c; register struct addr *ap; register FILE *fp; for(ap = mp->to;ap != NULLADDR;ap = ap->next){ fseek(mp->data,0L,0); /* rewind */ fp = fopen(ap->val,"a+"); while((c = getc(mp->data)) != EOF){ if(putc(c,fp) == EOF) break; } if(ferror(fp)){ fclose(fp); tprintf(mp->tcb,mboxerr,ap->val); fclose(mp->data); mp->data = NULLFILE; return; } fclose(fp); } tprintf(mp->tcb,sent); fclose(mp->data); mp->data = NULLFILE; }