/* SMTP Server state machine - see RFC 821 * Very simple implementation; no forwarding allowed * (who wants to re-create "sendmail" ??) * enhanced 12/87 Dave Trulli nn2z */ #include #include #include #include "global.h" #include "mbuf.h" #include "netuser.h" #include "timer.h" #include "tcp.h" #include "smtp.h" #ifndef DFLT_MODE #define DFLT_MODE 0660 /* use this instead of user's umask */ #endif char *ptime(), *getname(); void mail_delete(), del_rcpt(); static int queuejob(),checkaddress(); int32 get_msgid(); /* 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, \"Gateway to the universe!\"\r\n"; /*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_SERVER,0,r_mail,NULLVFP,s_mail,0,(char *)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; } (void) tprintf(mp->tcb,banner,hostname); log(tcb,"open SMTP"); break; case CLOSE_WAIT: close_tcp(tcb); break; case CLOSED: log(tcb,"close SMTP"); mp = (struct mail *)tcb->user; mail_delete(mp); 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 *inet_ntoa(),c; struct mbuf *bp; void docommand(),deliver(),doline(); if((mp = (struct mail *)tcb->user) == NULLMAIL){ /* Unknown sessioo */ 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 */ #ifdef MSDOS case '\032': /* Strip ctrl/Z's */ #endif continue; case '\n': /* Complete line; process it */ mp->buf[mp->cnt] = '\0'; doline(mp); break; default: /* Assemble line */ if(mp->cnt != LINELEN-1) mp->buf[mp->cnt++] = c; break; } } } /* Process Process Process Process Process ipient */ if((cp = getname(arg)) == NULLCHAR){ (void) tprintf(mp->tcb,syntax); break; } if (checkaddress(cp)) { (void) tprintf(mp->tcb,badname,cp); break; } /* 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); break; } if((ap->val = malloc((unsigned)strlen(cp)+1)) == NULLCHAR){ free((char *)ap); close_tcp(mp->tcb); break; } strcpy(ap->val,cp); ap->next = mp->to; mp->to = ap; (void) tprintf(mp->tcb,ok); break; case HELP_CMD: (void) tprintf(mp->tcb,help); break; case DATA_CMD: if(mp->to == NULLADDR){ (void) tprintf(mp->tcb,needrcpt); break; } tcp_output(mp->tcb); /* Send ACK; disk I/O is slow */ if((mp->data = tmpfile()) == NULLFILE){ (void) tprintf(mp->tcb,ioerr); break; } /* Add timestamp; ptime adds newline */ mp->seqn = get_msgid(); time(&t); fprintf(mp->data,"Received: "); if(mp->system != NULLCHAR) fprintf(mp->data,"from %s ",mp->system); fprintf(mp->data,"by %s with SMTP (871225.4/ST)\n\tid %ld; %s", hostname, mp->seqn, ptime(&t)); if(ferror(mp->data)){ (void) tprintf(mp->tcb,ioerr); } else { mp->state = DATA_STATE; (void) tprintf(mp->tcb,enter); } break; case RSET_CMD: del_rcpt(mp->to); mp->to = NULLADDR; mp->state = COMMAND_STATE; (void) 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; { register char *cp1; 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; { int c; register struct addr *ap; register FILE *fp; char mailbox[50]; char *cp; int fail = 0; for(ap = mp->to;ap != NULLADDR;ap = ap->next) { /* * For now just look at the user name of the address * more in next release. nn2z */ if ((cp = index(ap->val,'@')) != NULLCHAR) *cp = '\0'; cp = ap->val; while( *cp && isalnum(*cp)) cp++; *cp = '\0'; fseek(mp->data,0L,0); /* rewind */ /* if mail file is busy save it in out smtp queue * and let the smtp daemon try later. */ if (mlock(mailspool,ap->val)) fail = queuejob(mp->data,hostname,ap->val,mp->from,mp->seqn); else { sprintf(mailbox,"%s%s.txt",mailspool,ap->val); if((fp = fopen(mailbox,"a+")) != NULLFILE) { while((c = getc(mp->data)) != EOF) if(putc(c,fp) == EOF) break; if(ferror(fp)) fail = 1; /* Leave a blank line between msgs */ fprintf(mp->data,"\n"); fclose(fp); #ifdef UNIX chmod(mailbox, DFLT_MODE); #endif } else fail = 1; (void) rmlock(mailspool,ap->val); if (fail) break; } } if (fail) (void) tprintf(mp->tcb,mboxerr,ap->val); else (void) tprintf(mp->tcb,sent); fclose(mp->data); mp->data = NULLFILE; del_rcpt(mp->to); mp->to = NULLADDR; } /* Return Date/Time in Arpanet format in passed string */ char * ptime(t) long *t; { register struct tm *ltm; struct tm *gmtime(); static char timezone[4]; static char str[40]; extern char *getenv(); /* Print out the time and date field as * "DAY day MONTH year hh:mm:ss ZONE" */ char *p; static char *days[7] = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" }; static char *months[12] = { "Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec" }; /* Read the system time */ ltm = gmtime(t); if (*timezone == '\0') if ((p = getenv("TZ")) == NULL) strcpy(timezone,"GMT"); else strncpy(timezone,p,3); /* rfc 822 format */ sprintf(str,"%s, %.2d %s %02d %02d:%02d:%02d %.3s\n", days[ltm->tm_wday], ltm->tm_mday, months[ltm->tm_mon], ltm->tm_year, ltm->tm_hour, ltm->tm_min, ltm->tm_sec, timezone); return(str); } int32 get_msgid() { char sfilename[LINELEN]; char s[20]; long sequence = 0; FILE *sfile; long atol(); strcpy(sfilename,mailqdir); strcat(sfilename,"sequence.seq"); sfile = fopen(sfilename,"r"); /* if sequence file exists, get the value, otherwise set it */ if (sfile != NULL) { (void) fgets(s,sizeof(s),sfile); sequence = atol(s); /* Keep it in range of and 8 digit number to use for dos name prefix. */ if (sequence < 0L || sequence > 99999999L ) sequence = 0; fclose(sfile); } /* increment sequence number, and write to sequence file */ sfile = fopen(sfilename,"w"); fprintf(sfile,"%ld",++sequence); fclose(sfile); #ifdef UNIX chmod(sfile, DFLT_MODE); #endif return sequence; } /* test if mail address is valid - to be improved in next release */ static int checkaddress(s) char *s; { FILE *fp; char mailbox[50]; char *cp; strcpy(mailbox,mailspool); cp = mailbox; while (*cp) /* find end of string */ cp++; while ( *s && isalnum(*s)) /*GRI MOD append the user name */ *cp++ = *s++; *cp = '\0'; strcat(mailbox,".txt"); /* and file type */ /* Check to see if we can open the mailbox */ if ((fp = fopen(mailbox,"a+")) == NULLFILE) return 1; fclose(fp); return 0; } /* place a mail job in the outbound queue */ static int queuejob(dfile,host,to,from,id) FILE *dfile; char *host,*to,*from; int32 id; { FILE *fp; char tmpstring[50]; char prefix[9]; int c; sprintf(prefix,"%.8d",id); mlock(mailqdir,prefix); sprintf(tmpstring,"%s%s.txt",mailqdir,prefix); if((fp = fopen(tmpstring,"w")) == NULLFILE) { (void) rmlock(mailqdir,prefix); return 1; } while((c = getc(dfile)) != EOF) if(putc(c,fp) == EOF) break; if(ferror(fp)){ fclose(fp); (void) rmlock(mailqdir,prefix); return 1; } fclose(fp); sprintf(tmpstring,"%s%s.wrk",mailqdir,prefix); if((fp = fopen(tmpstring,"w")) == NULLFILE) { (void) rmlock(mailqdir,prefix); return 1; } fprintf(fp,"%s\n%s\n%s\n",host,from,to); fclose(fp); (void) rmlock(mailqdir,prefix); return 0; }