# /* EMACS_MODES: c !fill */ /* smtpd.c * * World's most trivial SMTP server. Only accepts the MAIL, FROM, * RCPT, and DATA commands. Generates a request file for the mail * daemon to process and kicks the mail daemon off. * * Uses the server version of DCtcp and hence must run under tasking. */ #include #include #include #include #include #include #include #include "task.h" #include "cmds.h" #define SMTPSOCK 25 /* smtp server socket */ #define WINDOW 1000 /* seems reasonable */ #define LOCKMODE 0444 /* mode for lock file */ #define DATAMODE 0660 /* mode for data file */ #define MAXTIME 1200 /* way too long - die */ char cmdbuf[BUFSIZ]; /* command buffer */ int buflen; /* size of string in cmd buffer */ char cirbuf[BUFSIZ]; /* circular buffer for reads */ char *cirput = cirbuf; /* put pointer for cirbuf */ char *cirget = cirbuf; /* get pointer for cirbuf */ FILE *rqfd; /* request file descriptor */ FILE *datafd; /* data file descriptor */ char reqname[NAMSIZ]; /* request file name */ char tmpname[NAMSIZ]; /* temp request file name */ char dataname[NAMSIZ]; /* data file name */ char *maildir = "/maild"; /* mail daemon's directory */ char *lockname = "/maild/smtp/lock"; /* lockfile */ char *sigmaild = "/bin/sig_maild"; /* mail daemon signaller */ task *tk_smtp; /* smtp task */ event open_done; /* channel open event */ event in_avail; /* command input done event */ event spc_avail; /* output buffer space available */ event data_done; /* data input done event */ int fclsd = FALSE; /* other end has closed connection */ int quitrcvd; /* debugging only */ int dbg; /* debug flag */ extern int cmd_rcv (), data_rcv (), us_fcls (), death (); extern int us_opnl (), us_cls (), us_tmo (), us_space(); extern int done (), quit_sig (), quit (); main (argc, argv) /* The main routine just gets tasking and TCP initiated and calls the * command processor. */ int argc; char **argv; { int flags; extern int die_loudly (); if (argc > 1) { sscanf (argv[1], "%d", &flags); if (flags & 1) dbg = TRUE; } chdir (maildir); if (!creat_lock (lockname)) { printf ("Duplicate daemon exiting\n"); exit (1); } signal (SIGINT, SIG_IGN); signal (SIGTERM, quit_sig); signal (SIGQUIT, die_loudly); if (!tcp_init (512, 0, us_opnl, cmd_rcv, us_cls, us_fcls, us_tmo, us_space)) exit (1); tk_smtp = tk_cur; if (flags & 2) { tcpdebug (TRUE); } if (!tcp_lstn (SMTPSOCK, WINDOW)) exit (0); do_cmds (); } do_cmds () /* This is the routine which processes incoming smtp commands from the * user. It goes to sleep awaiting network input. When a complete * command is received, the tcp receiver task awakens us to process it. * Currently only the commands listed in the command table are accepted. * This routine never returns. */ { while (!tst_and_clr (&open_done)) tk_block (); tm_set (MAXTIME, death, 0, 0); /* make sure we eventually go away */ tputs ("220 MIT-CSR.MIT Simple Mail Transfer Service\n"); do_helo (); /* wait for the hello */ for (;;) { /* until he quits */ do_mail (); /* wait for the mail command */ while (do_rcpt ()) ; /* do all the recipients */ do_data (); /* do the data */ } } do_helo () /* Wait for the user to send the HELO command. Punt out if he sends * QUIT or RSET. */ { int cmd; for (;;) { buflen = tgets (cmdbuf); /* wait for command */ switch (cmd = cmdparse (cmdbuf, buflen)) { case QUIT: case RSET: quit (); case NOOP: tputs ("250 OK\n"); continue; case HELO: tputs ("250 MIT-CSR.MIT\n"); return; case NONE: tputs ("502 Unimplemented command\n"); continue; default: tputs ("503 Expecting HELO\n"); continue; } } } do_mail () /* Wait for the user to send the MAIL command. Punt out if he sends * QUIT or RSET. */ { int cmd; for (;;) { buflen = tgets (cmdbuf); /* wait for command */ switch (cmd = cmdparse (cmdbuf, buflen)) { case QUIT: case RSET: quit (); case NOOP: tputs ("250 OK\n"); continue; case MAIL: /* Should save FROM: line here, etc... */ if (!init_xfr ()) { /* set up request file, etc. */ tputs ("451 Can't initialize transfer\n"); continue; } tputs ("250 OK\n"); return; case NONE: tputs ("502 Unimplemented command\n"); continue; default: tputs ("503 Expecting MAIL\n"); continue; } } } do_rcpt () /* Wait for the user to send the RCPT command. Punt out if he sends * QUIT or RSET. Returns TRUE if a RCPT command was received, FALSE * if a DATA command was received. */ { int cmd; for (;;) { buflen = tgets (cmdbuf); /* wait for command */ switch (cmd = cmdparse (cmdbuf, buflen)) { case QUIT: case RSET: quit (); case NOOP: tputs ("250 OK\n"); continue; case RCPT: if (!parse_rcpt (cmdbuf, buflen)) { tputs ("501 Syntax error in recipient name\n"); continue; } tputs ("250 OK\n"); return (TRUE); case DATA: tputs ("354 Start mail input; end with .\n"); return (FALSE); case NONE: tputs ("502 Unimplemented command\n"); continue; default: tputs ("503 Expecting RCPT or DATA\n"); continue; } } } do_data () /* Perform the data transfer. Actually, all of the work of writing the * data out to the file will be done by the routine data_rcv() which is * called by the tcp receiver task when the data is actually received. * So after setting up for that routine to be called, this procedure * just sleeps until the transfer is completed. It then finishes up * the transfer (closing the data file, renaming the request file, etc.) * and sets up the cmd_rcv() routine for the next transfer before * returning. */ { int pid; extern int (*tc_dispose)(); /* routine called on data rcvd */ tc_dispose = data_rcv; while (!tst_and_clr (&data_done)) tk_block (); tc_dispose = cmd_rcv; fclose (rqfd); fclose (datafd); link (tmpname, reqname); /* rename request file to .req */ unlink (tmpname); tmpname[0] = 0; /* null out names for quit */ dataname[0] = 0; reqname[0] = 0; tputs ("250 OK\n"); if ((pid = fork ()) == 0) /* signal the mailer daemon */ execl (sigmaild, sigmaild, 0); } us_opnl () /* Called when a connection is opened to some host requesting service. * Tell him our name, etc. */ { tk_setef (tk_smtp, &open_done); } creat_lock (name) /* Try to create a lock file with the specified name and write our * process id out there. Returns TRUE on success, FALSE on failure. */ char *name; { int fd; /* file descriptor */ FILE *fptr; /* for stdio */ if ((fd = creat (name, LOCKMODE)) < 0) return (FALSE); fptr = fcons (fd, "w"); /* cons up stdio descriptor */ fprintf (fptr, "%d\n", getpid ()); fclose (fptr); return (TRUE); } init_xfr () /* Create the request file and data file for the transfer. Get unique * names and create the files. */ { int dfd; /* file desc. for data file */ tmpnam (tmpname); strcpy (reqname, tmpname); strcat (reqname, ".req"); tmpnam (dataname); if ((dfd = creat (dataname, DATAMODE)) < 0) return (FALSE); datafd = fcons (dfd, "w"); /* make stdio descriptor */ if ((rqfd = fopen (tmpname, "w")) == NULL) { fclose (datafd); unlink (dataname); return (FALSE); } fwrite (dataname, NAMSIZ, 1, rqfd); return (TRUE); } quit () /* Give up on the transfer. Unlink the data and request files (if any), * close the tcp connection, and block. The process will exit when the * tcp connection successfully closes. */ { quitrcvd = TRUE; tputs ("221 MIT-CSR.MIT Simple Mail Transfer Service Terminating\n"); tcp_close (); for (;;) tk_block (); } cmdparse (buf, len) /* Parse the command part off the specified buffer. Return the index * of the command in the command table (or 0 if the command is not * recognized). * The commands and indices accepted are listed in the include file * "cmds.h". */ char *buf; /* buffer */ int len; /* length */ { register char *p, *q; /* temp ptrs. */ register struct cmdtab *ct; /* cmd table ptr */ int clen; /* length of this command */ int i; /* index in cmd table */ for (ct = &cmdtab[1], i = 1; ct->c_name != NULL; ct++, i++) { clen = ct->c_len; if (len < clen) continue; for (p = ct->c_name, q = buf; clen > 0 && *p == toupper (*q); p++, q++, clen--) ; if (clen == 0) /* success */ return (i); } return (0); } static char *to; /* ptr. into request buffer */ parse_rcpt (buf, len) /* Parse the recipient spec in the buffer. Start by stripping the * command off the front of the buffer. Then call canon() to convert * the recpient name into a format acceptable to the mailer daemon * (ie. the original multiple-at-sign format). * Returns TRUE if parsed successfully, FALSE otherwise. * * Arguments: */ char *buf; /* command buffer */ int len; /* size of buffer string */ { register char *from; /* ptr to recipient name */ struct req_rec req; /* request file record */ from = &buf[cmdtab[RCPT].c_len]; if (*from++ != '<') return (FALSE); to = req.rq_name; /* set up to pointer */ if (!canon (from)) /* canonicalize into req buffer */ return (FALSE); printf ("parsed ok: %s\n", req.rq_name); req.rq_retries = RETRIES; fwrite (&req, 1, sizeof (req), rqfd); /* write it out */ return (TRUE); } canon (from) /* Canonicalize the smtp-style path pointed to by from into the buffer * pointed to by the external static variable to. The result will be * a string containing the multiple-at-sign form, as desired by the * mailer daemon. Also removes the '\' escape characters. * The procedure follwed is recursive: this routine is recursively * called for each "@host" in the from string. * Returns TRUE if successful, or FALSE if the format of the recipient * name is bad. * * Arguments: */ register char *from; /* start of string to canonicalize */ { register char *end; /* end of this part of path */ register int escseen; /* escape character seen */ int atseen; /* '@' seen in mailbox */ escseen = atseen = FALSE; if (*from == '@') { /* host name; find end */ for (end = from; *end != '\0'; end++) { if (escseen) escseen = FALSE; else if (*end == '\\') /* escape? */ escseen = TRUE; else if (*end == ',' || *end == ':') break; } if (*end == '\0' || !canon (end+1)) { /* bad format? */ printf ("no mailbox found\n"); return (FALSE); } else { escseen = FALSE; for (; from < end; from++) { /* copy into to buffer */ if (escseen) escseen = FALSE; else if (*from == '\\') { escseen = TRUE; continue; } *to++ = *from; } *to = '\0'; return (TRUE); } } else { for (; *from != '\0'; from++) { /* copy mailbox */ if (escseen) escseen = FALSE; else if (*from == '\\') { escseen = TRUE; continue; } else if (*from == '>') { /* end of string? */ *to = '\0'; return (atseen); } else if (*from == '@') { /* end of username? */ printf ("found @ in mailbox\n"); atseen = TRUE; } *to++ = *from; } printf ("no > at end of string\n"); return (FALSE); /* no '>' seen */ } } cmd_rcv (buf, len, urg) /* Called by the tcp receive task on receipt of a command from the net * Just copy the data into the circular buffer and wake up the smtp task * to process the data. * * Arguments: */ char *buf; /* data buffer */ int len; /* data length */ int urg; /* urgent ptr (unused) */ { register char *p, *q; register int i; for (p = cirput, q = buf, i = len; i > 0; i--) { /* copy the data */ if (p == &cirbuf[BUFSIZ]) p = cirbuf; *p++ = *q++; } cirput = p; tk_setef (tk_smtp, &in_avail); } tgets (buf) /* Wait for a full command line to be input. As the characters come in, * they are copied into the circular buffer. This routine takes them * out and copies them into the specified buffer for processing. It * performs the netascii conversion on the fly. Returns the number of * characters input. * * Arguments: */ char *buf; /* buffer address */ { register char *p, *q; /* temp pointers */ register int i; /* length counter */ static int crseen = FALSE; /* for netascii conversion */ for (q = buf, i = 0;;) { for (p = cirget; p != cirput; p++) { if (p == &cirbuf[BUFSIZ]) p = cirbuf; if (crseen) { /* need to de-netascify */ crseen = FALSE; if (*p == '\n') { *q++ = '\n'; /* end of line */ *q = '\0'; /* null terminate */ i++; cirget = ++p; /* clean up */ if (dbg) printf ("%s", buf); return (i); } else { *q++ = '\r'; i++; if (*p != '\0') { *q++ = *p; i++; } } } else if (*p == '\r') { crseen = TRUE; } else { *q++ = *p; i++; } } cirget = p; /* fix get ptr. back up */ /* Full line not in yet; wait for character input */ while (!tst_and_clr (&in_avail)) { if (fclsd) die_fclsd(); /* won't return */ tk_block (); } } } /* Netascii definitions for data reception */ #define NORM 0 #define CRSEEN 1 #define NLSEEN 2 #define DOTSEEN 3 data_rcv (buf, len, urg) /* Called from tcp receive level on receipt of data. Writes the data out * into the mail file. Checks for the end-of-data sequence (.) * and wakes up the smtp task when it is received. * * Arguments: */ register char *buf; register int len; int urg; { static int data_state = NORM; while (len > 0) { switch (data_state) { case CRSEEN: if (*buf == '\n') { putc ('\n', datafd); data_state = NLSEEN; } else { data_state = NORM; putc ('\r', datafd); if (*buf != '\0') putc (*buf, datafd); } break; case NLSEEN: if (*buf == '.') data_state = DOTSEEN; else { data_state = NORM; continue; } break; case DOTSEEN: data_state = NORM; if (*buf == '\r') { tk_setef (tk_smtp, &data_done); return; } else putc (*buf, datafd); break; case NORM: if (*buf == '\r') data_state = CRSEEN; else putc (*buf, datafd); break; } buf++; len--; } } tputs (str) /* Send the specified string out to the net. Do the appropriate netascii * conversion as it goes. * * Arguments: */ register char *str; { extern task *TCPsend; extern event sendef; if (dbg) printf ("%s", str); for (; *str != '\0'; str++) { if (*str == '\n') tputc ('\r'); tputc (*str); if (*str == '\r') tputc ('\0'); } tk_setef (TCPsend, &sendef); } tputc (c) /* Put the specified character out to tcp. If the output buffer is full, * block until reawakened by the tcp (via the us_space routine). * * Arguments: */ char c; /* character to output */ { while (!tc_put (c)) tk_block (); } us_tmo () /* Called on retransmit too long. Unlink the request and data files and * quit. */ { extern int opening, ourclosing, hisclosing; extern in_name forhost; if (tmpname[0] != 0) unlink (tmpname); if (dataname[0] != 0) unlink (dataname); printf ("Dying with timeout - retransmit to %X too long\n", forhost); printf ("opening = %d, ourclosing = %d, hisclosing = %d\n", opening, ourclosing, hisclosing); exit (1); } us_space () /* Called by the tcp layer when space becomes available in the tcp output * buffer. Just wakes up the smtp task if it's asleep. */ { tk_setef (tk_smtp, &spc_avail); } us_cls () { extern int opening, ourclosing, hisclosing; printf ("Exiting normally\n"); printf ("opening = %d, ourclosing = %d, hisclosing = %d\n", opening, ourclosing, hisclosing); unlink (dataname); unlink (tmpname); exit (0); } quit_sig() { unlink (lockname); exit (0); } us_fcls () { if (!quitrcvd) { printf ("Barf!! He closed connection without quitting!\n"); fclsd = TRUE; /* tcp_close (); */ } } die_loudly () { fflush (stdout); abort (); } die_fclsd () { extern int opening, ourclosing, hisclosing; printf ("Conn. closed without QUIT.\nopening = %d, ourclosing = %d, hisclosing = %d\n", opening, ourclosing, hisclosing); if (tmpname[0] != '\0') { unlink (dataname); unlink (tmpname); } exit (1); } death () /* Maximum time to live elapsed. Die right now. */ { extern int opening, ourclosing, hisclosing; printf ("Max transfer length timeout.\nopening = %d, ourclosing = %d, hisclosing = %d\n", opening, ourclosing, hisclosing); if (tmpname[0] != '\0') { unlink (dataname); unlink (tmpname); } exit (1); }