/* * Simple Mail Transfer Protocol (SMTP) routines * * Mark Crispin, SUMEX Computer Project, 27 July 1988 * * Copyright (c) 1988 Stanford University * */ #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "smtp.h" #include "tcpsio.h" #include "memory.h" #include "misc.h" void smtp_quit(); void smtp_rcpt(); void smtp_address(); void rfc822_header(); void rfc822_write_address(); void mtp_free_message(); void mtp_free_address(); void mtp_close(); int smtp_reply(); int smtp_send(); int smtp_data(); int smtp_fake(); void mtp_debug(); extern void usrlog(); extern void dbglog(); extern tcp_open(); extern atoi(); /* Mail Transfer Protocol routines * * mtp_xxx routines are the interface between this module and the outside * world. Only these routines should be referenced by external callers. * The idea is that if SMTP is ever replaced by some other mail transfer * protocol this discipline may reduce the amount of conversion effort. * */ /* Mail Transfer Protocol open connection * Accepts: service host list * initial debugging flag * Returns: T on success, NIL on failure */ SMTPSTREAM *mtp_open (hostlist,debug) char **hostlist; int debug; { SMTPSTREAM *stream = NIL; while (*hostlist) { /* try all servers */ /* open connection */ if (stream = smtp_open (*hostlist)) { if (debug) mtp_debug (stream); /* get SMTP greeting */ if ((smtp_reply (stream)) == SMTPGREET) /* negotiate HELO */ if ((smtp_send (stream,"HELO",tcp_localhost (stream->tcpstream))) == SMTPOK) return (stream); mtp_close (stream); /* otherwise punt stream */ } ++hostlist; /* and try next server */ } return (NIL); } /* Mail Transfer Protocol close connection * Accepts: stream */ void mtp_close (stream) SMTPSTREAM *stream; { smtp_quit (stream); } /* Mail Transfer Protocol deliver mail * Accepts: stream * delivery option (MAIL, SEND, SAML, SOML) * message * Returns: T on success, NIL on failure */ int mtp_mail (stream,type,msg) SMTPSTREAM *stream; char *type; MESSAGE *msg; { char tmp[256]; int error = NIL; if (!(msg->to || msg->cc || msg->bcc)) { /* no recipients in request */ smtp_fake (stream,SMTPHARDERROR,"No recipients specified"); return (NIL); } /* make sure stream is in good shape */ smtp_send (stream,"RSET",NIL); strcpy (tmp,"FROM:<"); /* compose "MAIL FROM:<return-path>" */ smtp_address (tmp,msg->return_path); strcat (tmp,">"); /* send "MAIL FROM" command */ if (!(smtp_send (stream,type,tmp) == SMTPOK)) return (NIL); /* negotiate the recipients */ if (msg->to) smtp_rcpt (stream,msg->to,&error); if (msg->cc) smtp_rcpt (stream,msg->cc,&error); if (msg->bcc) smtp_rcpt (stream,msg->bcc,&error); if (error) { /* any recipients failed? */ /* reset the stream */ smtp_send (stream,"RSET",NIL); smtp_fake (stream,SMTPHARDERROR,"One or more recipients failed"); return (NIL); } /* looks good, send the data and return */ return (smtp_data (stream,msg)); } /* Mail Transfer Protocol parse address * Accepts: input string * default host name * Returns: address list */ MTPADR *mtp_parse_address (string,defaulthost) char *string; char *defaulthost; { char tmp[1024]; MTPADR *first = NIL; MTPADR *last; MTPADR *adr; /* handle empty string */ if (string[0] == '\0') return (NIL); while (string) { /* loop until string exhausted */ /* got an address? */ if (adr = rfc822_parse_address (&string,defaulthost)) { if (!first) { /* yes, first time through? */ first = adr; /* yes, remember this first address */ } else last->next = adr; /* otherwise, append to the list */ last = adr; /* set for subsequent linking */ } else { /* bad mailbox */ sprintf (tmp,"Bad mailbox: %s",string); usrlog (tmp); string = NIL; /* punt parse */ } } return (first); /* return address list */ } /* Mail Transfer Protocol turn on debugging telemetry * Accepts: stream */ void mtp_debug (stream) SMTPSTREAM *stream; { stream->debug = T; /* turn on protocol telemetry */ } /* Mail Transfer Protocol turn off debugging telemetry * Accepts: stream */ mtp_nodebug (stream) SMTPSTREAM *stream; { stream->debug = NIL; /* turn off protocol telemetry */ } /* Simple Mail Transfer Protocol routines * * smtp_xxx routines are internal routines called only by the mtp_xxx * routines. * */ /* Simple Mail Transfer Protocol open connection * Accepts: host name * message * Returns: SMTP stream on success, NIL otherwise */ SMTPSTREAM *smtp_open (host) char *host; { SMTPSTREAM *stream = NIL; int tcpstream; /* connect to host on SMTP port */ if (tcpstream = tcp_open (host,SMTPTCPPORT)) { stream = (SMTPSTREAM *) malloc (sizeof (SMTPSTREAM)); /* bind our stream */ stream->tcpstream = tcpstream; stream->lock = NIL; stream->debug = NIL; stream->reply = NIL; } return (stream); } /* Simple Mail Transfer Protocol quit connection * Accepts: SMTP stream */ void smtp_quit (stream) SMTPSTREAM *stream; { if (stream) { /* send "QUIT" */ smtp_send (stream,"QUIT",NIL); /* close TCP connection */ tcp_close (stream->tcpstream); if (stream->reply) free ((char *) stream->reply); free ((char *) stream); /* flush the stream */ } } /* Simple Mail Transfer Protocol send recipient * Accepts: SMTP stream * address list * pointer to error flag */ void smtp_rcpt (stream,adr,error) SMTPSTREAM *stream; MTPADR *adr; int *error; { char tmp[256]; while (adr) { if (adr->error) { /* clear any former error */ free (adr->error); adr->error = NIL; } strcpy (tmp,"TO:<"); /* compose "RCPT TO:<return-path>" */ smtp_address (tmp,adr); strcat (tmp,">"); /* send "RCPT TO" command */ if (!(smtp_send (stream,"RCPT",tmp) == SMTPOK)) { *error = T; /* note that an error occurred */ adr->error = malloc (strlen (stream->reply)); strcpy (adr->error,stream->reply); } adr = adr->next; /* do any subsequent recipients */ } } /* Simple Mail Transfer Protocol write address to string * Accepts: pointer to destination string * address to interpret */ void smtp_address (dest,adr) char *dest; MTPADR *adr; { if (adr) { /* no-op if no address */ if (adr->routeList) { /* have an A-D-L? */ strcat (dest,adr->routeList); strcat (dest,":"); } strcat (dest,adr->mailBox); /* write mailbox name */ strcat (dest,"@"); /* host delimiter */ strcat (dest,adr->host); /* write host name */ } } /* Simple Mail Transfer Protocol send message data * Accepts: SMTP stream * message * Returns: T on success, NIL on failure */ int smtp_data (stream,msg) SMTPSTREAM *stream; MESSAGE *msg; { char header[8196]; /* hope it's big enough */ /* get RFC822 header */ rfc822_header (header,msg); /* negotiate data command */ if (!(smtp_send (stream,"DATA",NIL) == SMTPREADY)) return (NIL); /* set up error in case failure */ smtp_fake (stream,SMTPSOFTFATAL,"SMTP connection went away!"); /* send header */ if (!(tcp_soutr (stream->tcpstream,header))) return (NIL); /* send text */ if (msg->text) if (strlen (msg->text)) if (!(tcp_soutr (stream->tcpstream,msg->text))) return (NIL); if (!(smtp_send (stream,"\015\012.",NIL) == SMTPOK)) return (NIL); return (T); } /* Simple Mail Transfer Protocol send command * Accepts: SMTP stream * text * Returns: reply code */ int smtp_send (stream,command,args) SMTPSTREAM *stream; char *command; char *args; { char tmp[1024]; /* build the complete command */ if (args) sprintf (tmp,"%s %s",command,args); else sprintf (tmp,command); if (stream->debug) dbglog (tmp); strcat (tmp,"\015\012"); /* send the command */ if (!(tcp_soutr (stream->tcpstream,tmp))) return (smtp_fake (stream,SMTPSOFTFATAL,"SMTP connection went away!")); return (smtp_reply (stream)); /* get reply and return it */ } /* Simple Mail Transfer Protocol get reply * Accepts: SMTP stream * Returns: reply code */ int smtp_reply (stream) SMTPSTREAM *stream; { /* flush old reply */ if (stream->reply) free ((char *) stream->reply); /* get reply */ if (!(stream->reply = tcp_getline (stream->tcpstream))) return (smtp_fake (stream,SMTPSOFTFATAL,"SMTP connection went away!")); if (stream->debug) dbglog (stream->reply); /* handle continuation by recursion */ if (stream->reply[3] == '-') return (smtp_reply (stream)); return (atoi (stream->reply));/* else return integer of reply code */ } /* Simply Mail Transfer Protocol set fake error * Accepts: SMTP stream * SMTP error code * error text * Returns: error code */ int smtp_fake (stream,code,text) SMTPSTREAM *stream; int code; char *text; { /* flush any old reply */ if (stream->reply ) free ((char *) stream->reply); /* set up pseudo-reply string */ stream->reply = malloc (20+strlen (text)); sprintf (stream->reply,"%d %s",code,text); return (code); /* return error code */ } /* RFC822 writing routines * * These routines are logically part of the smtp_xxx routines above, but * have been separated out here in case some external caller would like to * use them. * */ /* Write RFC822 header from message structure * Accepts: pointer to destination string * message to interpret */ void rfc822_header (header,msg) char *header; MESSAGE *msg; { strcpy (header,"Date: "); /* start off with Date: line */ if (!msg->date) msg->date = "???"; strcat (header,msg->date); if (msg->from) { /* write From: line */ strcat (header,"\015\012From: "); rfc822_write_address (header,msg->from); } if (msg->sender) { /* write Sender: line */ strcat (header,"\015\012Sender: "); rfc822_write_address (header,msg->sender); } if (msg->reply_to) { /* write Reply-To: line */ strcat (header,"\015\012Reply-To: "); rfc822_write_address (header,msg->reply_to); } if (msg->subject) { /* write Subject: line */ strcat (header,"\015\012Subject: "); strcat (header,msg->subject); } if (msg->to) { /* write To: line */ strcat (header,"\015\012To: "); rfc822_write_address (header,msg->to); } if (msg->cc) { /* write cc: line */ strcat (header,"\015\012cc: "); rfc822_write_address (header,msg->cc); } if (msg->in_reply_to) { /* write In-Reply-To: line */ strcat (header,"\015\012In-Reply-To: "); strcat (header,msg->in_reply_to); } if (msg->message_id) { /* write Message-ID: line */ strcat (header,"\015\012Message-ID: "); strcat (header,msg->message_id); } /* write terminating blank line */ strcat (header,"\015\012\015\012"); } /* Write RFC822 address * Accepts: pointer to destination string * address to interpret */ void rfc822_write_address (dest,adr) char *dest; MTPADR *adr; { while (adr) { if (adr->personalName) { /* if have a personal name */ /* write it */ strcat (dest,adr->personalName); strcat (dest," <"); /* write address delimiter */ smtp_address (dest,adr); /* write address */ strcat (dest,">"); /* closing delimiter */ } /* just write address */ else smtp_address (dest,adr); adr = adr->next; /* get next recipient */ if (adr) strcat (dest,", ");/* delimit if there is one */ } } /* RFC822 parsing routines * * These routines are intended to be called only from mtp_parse_address. * */ /* Parse RFC822 address * Accepts: pointer to string pointer * default host * Returns: address * * Updates string pointer */ MTPADR *rfc822_parse_address (string,defaulthost) char **string; char *defaulthost; { char tmp[1024]; MTPADR *adr; char *st; char *phrase; if (!string) return (NIL); /* flush leading whitespace */ if (*string[0] == ' ') ++*string; st = *string; /* note start of string */ /* get phrase if any */ phrase = rfc822_parse_phrase (st); /* This is much more complicated than it should be because users like * to write local addrspecs without "@localhost". This makes it very * difficult to tell a phrase from an addrspec! */ if (phrase && (adr = rfc822_parse_routeaddr (phrase,string,defaulthost))) { phrase[0] = '\0'; /* tie off phrase */ /* phrase is a personal name */ adr->personalName = malloc (1+strlen (st)); strcpy (adr->personalName,st); } else adr = rfc822_parse_addrspec (st,string,defaulthost); if (*string) { /* anything left? */ /* yes, better be comma */ if (*string[0] == ',') ++*string; else { sprintf (tmp,"Junk at end of line: %s",*string); usrlog (tmp); *string = NIL; /* punt remainder of parse */ } } return (adr); /* return the address */ } /* Parse RFC822 route-address * Accepts: string pointer * pointer to string pointer to update * Returns: address * * Updates string pointer */ MTPADR *rfc822_parse_routeaddr (string,ret,defaulthost) char *string; char **ret; char *defaulthost; { char tmp[1024]; MTPADR *adr; char *routelist = NIL; char *routeend = NIL; if (!string) return (NIL); /* flush leading whitespace */ if (string[0] == ' ') ++string; /* must start with open broket */ if (string[0] != '<') return (NIL); if (string[1] == '@') { /* have an A-D-L? */ routelist = ++string; /* yes, remember that fact */ while (string[0] != ':') { /* search for end of A-D-L */ /* punt if never found */ if (string[0] == '\0') return (NIL); ++string; /* try next character */ } string[0] = '\0'; /* tie off A-D-L */ routeend = string; /* remember in case need to put back */ } /* parse address spec */ if (!(adr = rfc822_parse_addrspec (++string,ret,defaulthost))) { /* put colon back since parse barfed */ if (routelist) routeend[0] = ':'; return (NIL); } if (routelist) { /* have an A-D-L? */ adr->routeList = malloc (1+strlen (routelist)); strcpy (adr->routeList,routelist); } /* make sure terminated OK */ if (*ret) if (*ret[0] == '>') { ++*ret; /* skip past the broket */ /* wipe pointer if at end of string */ if (*ret[0] == '\0') *ret = NIL; return (adr); /* return the address */ } sprintf (tmp,"Unterminated mailbox: %s@%s",adr->mailBox,adr->host); usrlog (tmp); return (adr); /* return the address */ } /* Parse RFC822 address-spec * Accepts: string pointer * pointer to string pointer to update * default host * Returns: address * * Updates string pointer */ MTPADR *rfc822_parse_addrspec (string,ret,defaulthost) char *string; char **ret; char *defaulthost; { MTPADR *adr; char *end; char c; if (!string) return (NIL); /* flush leading whitespace */ if (string[0] == ' ') ++string; /* find end of mailbox */ if (!(end = rfc822_parse_word (string,NIL))) return (NIL); /* create address block */ adr = (MTPADR *) malloc (sizeof (MTPADR)); adr->personalName = NIL; /* no personal name */ adr->routeList = NIL; /* no A-D-L */ adr->error = NIL; /* no error */ adr->next = NIL; /* no links */ c = end[0]; /* remember delimiter */ end[0] = '\0'; /* tie off mailbox */ while (c == ' ') { /* skip past whitespace */ ++end; /* try next character */ c = end[0]; } /* copy mailbox */ adr->mailBox = malloc (1+strlen (string)); strcpy (adr->mailBox,string); if (c == '@') { /* have host name? */ *ret = end; /* update return pointer */ string = ++end; /* skip delimiter */ /* search for end of host */ end = rfc822_parse_word (string," ()<>@,;:\"\\"); if (end) { /* got host name, copy it */ c = end[0]; /* remember delimiter */ end[0] = '\0'; /* tie off host name */ while (c == ' ') { /* skip past whitespace */ ++end; /* try next */ c = end[0]; } /* copy host */ adr->host = malloc (1+strlen (string)); strcpy (adr->host,string); end[0] = c; /* put delimiter back in string */ *ret = end; /* return new end pointer */ } else { /* bogus input, shove in default */ adr->host = malloc (1+strlen (defaulthost)); strcpy (adr->host,defaulthost); } } else { /* no host name */ end[0] = c; /* put delimiter back in string */ *ret = end; /* update return pointer */ /* and default host to current host */ adr->host = malloc (1+strlen (defaulthost)); strcpy (adr->host,defaulthost); } /* wipe pointer if at end of string */ if (*ret[0] == '\0') *ret = NIL; return (adr); /* return the address we got */ } /* Parse RFC822 phrase * Accepts: string pointer * Returns: pointer to end of phrase */ char *rfc822_parse_phrase (string) char *string; { char *curpos; if (!string) return (NIL); /* find first word of phrase */ curpos = rfc822_parse_word (string,NIL); if (!curpos) return (NIL); /* no words means no phrase */ /* check if string ends with word */ if (curpos[0] == '\0') return (curpos); string = curpos; /* sniff past the end of this word */ /* flush leading whitespace */ if (string[0] == ' ') ++string; /* recurse to see if any more */ if (string = rfc822_parse_phrase (string)) return (string); return (curpos); /* otherwise return this position */ } /* Parse RFC822 word * Accepts: string pointer * Returns: pointer to end of word */ char *rfc822_parse_word (string,delimiters) char *string; char *delimiters; { char *st,*str; if (!string) return (NIL); /* flush leading whitespace */ if (string[0] == ' ') ++string; /* end of string */ if (string[0] == '\0') return (NIL); if (string[0] == '"') { /* quoted string? */ ++string; /* yes, skip the open quote */ while (string[0] != '"') { /* look for close quote */ /* unbalanced quote check */ if (string[0] == '\0') return (NIL); /* handle quoted character */ if (string[0] == '\\') ++string; ++string; /* check next character */ } return (++string); /* return the pointer past the word */ } /* default delimiters to standard */ if (!delimiters) delimiters = " ()<>@,;:\"[\\]"; str = string; /* hunt pointer for strpbrk */ while (T) { /* look for delimiter */ st = (char *) strpbrk (str,delimiters); if (!st) { /* no delimiter, hunt for end */ while (string[0] != '\0') {++string;} return (string); /* return it */ } if (st[0] != '\\') break; /* found a word delimiter */ str = ++st; /* skip quoted character and go on */ } /* lose if at the first character */ if (st == string) return (NIL); return (st); /* otherwise, we have the word */ } /* SMTP utility routines */ /* Mail Transfer Protocol garbage collect message * Accepts: message */ void mtp_free_message (msg) MESSAGE *msg; { if (msg->date) free (msg->date); if (msg->subject) free (msg->subject); if (msg->return_path) mtp_free_address (msg->return_path); if (msg->from) mtp_free_address (msg->from); if (msg->sender) mtp_free_address (msg->sender); if (msg->reply_to) mtp_free_address (msg->reply_to); if (msg->to) mtp_free_address (msg->to); if (msg->cc) mtp_free_address (msg->cc); if (msg->bcc) mtp_free_address (msg->bcc); if (msg->in_reply_to) free (msg->in_reply_to); if (msg->message_id) free (msg->message_id); if (msg->text) free (msg->text); free ((char *) msg); /* finally free the message */ } /* Mail Transfer Protocol garbage collect address * Accepts: address */ void mtp_free_address (adr) MTPADR *adr; { if (adr->personalName) free (adr->personalName); if (adr->routeList) free (adr->routeList); if (adr->mailBox) free (adr->mailBox); if (adr->host) free (adr->host); if (adr->error) free (adr->error); if (adr->next) mtp_free_address (adr->next); free ((char *) adr); /* finally free the address */ }