home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.uv.es
/
2014.11.ftp.uv.es.tar
/
ftp.uv.es
/
pub
/
unix
/
pine4.10.tar.gz
/
pine4.10.tar
/
pine4.10
/
imap
/
src
/
c-client
/
smtp.c
< prev
next >
Wrap
C/C++ Source or Header
|
1998-12-01
|
21KB
|
609 lines
/*
* Program: Simple Mail Transfer Protocol (SMTP) routines
*
* Author: Mark Crispin
* Networks and Distributed Computing
* Computing & Communications
* University of Washington
* Administration Building, AG-44
* Seattle, WA 98195
* Internet: MRC@CAC.Washington.EDU
*
* Date: 27 July 1988
* Last Edited: 1 December 1998
*
* Sponsorship: The original version of this work was developed in the
* Symbolic Systems Resources Group of the Knowledge Systems
* Laboratory at Stanford University in 1987-88, and was funded
* by the Biomedical Research Technology Program of the National
* Institutes of Health under grant number RR-00785.
*
* Original version Copyright 1988 by The Leland Stanford Junior University
* Copyright 1998 by the University of Washington
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose and without fee is hereby granted, provided
* that the above copyright notices appear in all copies and that both the
* above copyright notices and this permission notice appear in supporting
* documentation, and that the name of the University of Washington or The
* Leland Stanford Junior University not be used in advertising or publicity
* pertaining to distribution of the software without specific, written prior
* permission. This software is made available "as is", and
* THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
* DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
* INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
* WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <ctype.h>
#include <stdio.h>
#include "mail.h"
#include "osdep.h"
#include "smtp.h"
#include "rfc822.h"
#include "misc.h"
/* Mailer parameters */
static unsigned long smtp_maxlogintrials = MAXLOGINTRIALS;
static long smtp_port = 0; /* default port override */
static long smtp_altport = 0;
static char *smtp_altname = NIL;
/* SMTP limits, current as of most recent draft */
#define SMTPMAXLOCALPART 64
#define SMTPMAXDOMAIN 255
#define SMTPMAXPATH 256
/* I have seen local parts of more than 64 octets, in spite of the SMTP
* limits. So, we'll have a more generous limit that's still guaranteed
* not to pop the buffer, and let the server worry about it. As of this
* writing, it comes out to 240. Anyone with a mailbox name larger than
* that is in serious need of a life or at least a new ISP! 23 June 1998
*/
#define MAXLOCALPART ((MAILTMPLEN - (SMTPMAXDOMAIN + SMTPMAXPATH + 32)) / 2)
/* Mail Transfer Protocol manipulate driver parameters
* Accepts: function code
* function-dependent value
* Returns: function-dependent return value
*/
void *smtp_parameters (long function,void *value)
{
switch ((int) function) {
case SET_MAXLOGINTRIALS:
smtp_maxlogintrials = (unsigned long) value;
break;
case GET_MAXLOGINTRIALS:
value = (void *) smtp_maxlogintrials;
break;
case SET_SMTPPORT:
smtp_port = (long) value;
break;
case GET_SMTPPORT:
value = (void *) smtp_port;
break;
case SET_ALTSMTPPORT:
smtp_altport = (long) value;
break;
case GET_ALTSMTPPORT:
value = (void *) smtp_altport;
break;
case SET_ALTSMTPNAME:
smtp_altname = (char *) value;
break;
case GET_ALTSMTPNAME:
value = (void *) smtp_altname;
break;
default:
value = NIL; /* error case */
break;
}
return value;
}
/* Mail Transfer Protocol open connection
* Accepts: network driver
* service host list
* port number
* service name
* SMTP open options
* Returns: SEND stream on success, NIL on failure
*/
SENDSTREAM *smtp_open_full (NETDRIVER *dv,char **hostlist,char *service,
unsigned long port,long options)
{
SENDSTREAM *stream = NIL;
long reply;
char *s,tmp[MAILTMPLEN];
NETSTREAM *netstream;
NETMBX mb;
if (!(hostlist && *hostlist)) mm_log ("Missing SMTP service host",ERROR);
/* maximum domain name is 64 characters */
else do if (strlen (*hostlist) < SMTPMAXDOMAIN) {
sprintf (tmp,"{%.1000s/%.20s}",*hostlist,service ? service : "smtp");
if (!mail_valid_net_parse (tmp,&mb) || mb.anoflag) {
sprintf (tmp,"Invalid host specifier: %.80s",*hostlist);
mm_log (tmp,ERROR);
}
else { /* did user supply port or service? */
if (mb.port || smtp_port)
sprintf (s = tmp,"%.1000s:%ld",mb.host,mb.port ? mb.port : smtp_port);
else s = mb.host; /* simple host name */
/* try to open ordinary connection */
if (netstream = mb.altflag ?
net_open ((NETDRIVER *) mail_parameters (NIL,GET_ALTDRIVER,NIL),s,
(char *) mail_parameters (NIL,GET_ALTSMTPNAME,NIL),
(unsigned long) mail_parameters(NIL,GET_ALTSMTPPORT,NIL)):
net_open (dv,s,mb.service,port)) {
stream = (SENDSTREAM *) memset (fs_get (sizeof (SENDSTREAM)),0,
sizeof (SENDSTREAM));
stream->netstream = netstream;
if (options & SOP_DEBUG) stream->debug = T;
if (options &(SOP_DSN | SOP_DSN_NOTIFY_FAILURE | SOP_DSN_NOTIFY_DELAY |
SOP_DSN_NOTIFY_SUCCESS | SOP_DSN_RETURN_FULL)) {
ESMTP.dsn.want = T;
if (options & SOP_DSN_NOTIFY_FAILURE) ESMTP.dsn.notify.failure = T;
if (options & SOP_DSN_NOTIFY_DELAY) ESMTP.dsn.notify.delay = T;
if (options & SOP_DSN_NOTIFY_SUCCESS) ESMTP.dsn.notify.success = T;
if (options & SOP_DSN_RETURN_FULL) ESMTP.dsn.full = T;
}
if (options & SOP_8BITMIME) ESMTP.eightbit.want = T;
/* get name of local host to use */
s = strcmp ("localhost",lcase (strcpy (tmp,mb.host))) ?
net_localhost (netstream) : "localhost";
do reply = smtp_reply (stream);
while ((reply < 100) || (stream->reply[3] == '-'));
if (reply != SMTPGREET){/* get SMTP greeting */
sprintf (tmp,"SMTP greeting failure: %.80s",stream->reply);
mm_log (tmp,ERROR);
stream = smtp_close (stream);
}
else if ((reply = smtp_ehlo (stream,s,&mb)) == SMTPOK) {
ESMTP.ok = T;
if (mb.secflag || mb.user[0]) {
if (ESMTP.auth) { /* have authenticators? */
if (!smtp_auth (stream,&mb,tmp)) stream = smtp_close(stream);
}
else { /* no available authenticators */
sprintf (tmp,"%sSMTP authentication not available: %.80s",
mb.secflag ? "Secure " : "",mb.host);
mm_log (tmp,ERROR);
stream = smtp_close (stream);
}
}
}
else if (mb.secflag || mb.user[0]) {
sprintf (tmp,"ESMTP failure: %.80s",stream->reply);
mm_log (tmp,ERROR);
stream = smtp_close (stream);
}
/* try ordinary SMTP then */
else if ((reply = smtp_send_work (stream,"HELO",s)) != SMTPOK) {
sprintf (tmp,"SMTP hello failure: %.80s",stream->reply);
mm_log (tmp,ERROR);
stream = smtp_close (stream);
}
}
}
} while (!stream && *++hostlist);
return stream;
}
/* SMTP authenticate
* Accepts: stream to login
* parsed network mailbox structure
* scratch buffer
* place to return user name
* Returns: T on success, NIL on failure
*/
long smtp_auth (SENDSTREAM *stream,NETMBX *mb,char *tmp)
{
unsigned long trial,auths;
char *lsterr = NIL;
char usr[MAILTMPLEN];
AUTHENTICATOR *at;
for (auths = ESMTP.auth; stream->netstream && auths &&
(at = mail_lookup_auth (find_rightmost_bit (&auths) + 1)); ) {
if (lsterr) { /* previous authenticator failed? */
sprintf (tmp,"Retrying using %s authentication after %s",
at->name,lsterr);
mm_log (tmp,NIL);
fs_give ((void **) &lsterr);
}
for (trial = 1; stream->netstream && trial &&
(trial <= smtp_maxlogintrials); )
if (smtp_send_work (stream,"AUTH",at->name)) {
if ((*at->client) (smtp_challenge,smtp_response,mb,stream,&trial,usr)&&
(stream->replycode == SMTPAUTHED)) return LONGT;
lsterr = cpystr (stream->reply);
}
}
if (lsterr) { /* previous authenticator failed? */
sprintf (tmp,"Can not authenticate to SMTP server: %s",lsterr);
mm_log (tmp,ERROR);
fs_give ((void **) &lsterr);
}
return NIL; /* authentication failed */
}
/* Get challenge to authenticator in binary
* Accepts: stream
* pointer to returned size
* Returns: challenge or NIL if not challenge
*/
void *smtp_challenge (void *s,unsigned long *len)
{
SENDSTREAM *stream = (SENDSTREAM *) s;
return (stream->replycode == SMTPAUTHREADY) ?
rfc822_base64 ((unsigned char *) stream->reply+4,
strlen (stream->reply+4),len) : NIL;
}
/* Send authenticator response in BASE64
* Accepts: MAIL stream
* string to send
* length of string
* Returns: T, always
*/
long smtp_response (void *s,char *response,unsigned long size)
{
SENDSTREAM *stream = (SENDSTREAM *) s;
unsigned long i,j;
char *t,*u;
if (response) { /* make CRLFless BASE64 string */
if (size) {
for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0;
j < i; j++) if (t[j] > ' ') *u++ = t[j];
*u = '\0'; /* tie off string */
i = smtp_send_work (stream,t,NIL);
fs_give ((void **) &t);
}
else i = smtp_send_work (stream,"",NIL);
}
/* abort requested */
else i = smtp_send_work (stream,"*",NIL);
return LONGT;
}
/* Mail Transfer Protocol close connection
* Accepts: SEND stream
* Returns: NIL always
*/
SENDSTREAM *smtp_close (SENDSTREAM *stream)
{
if (stream) { /* send "QUIT" */
smtp_send_work (stream,"QUIT",NIL);
/* close TCP connection */
net_close (stream->netstream);
if (stream->reply) fs_give ((void **) &stream->reply);
fs_give ((void **) &stream);/* flush the stream */
}
return NIL;
}
/* Mail Transfer Protocol deliver mail
* Accepts: SEND stream
* delivery option (MAIL, SEND, SAML, SOML)
* message envelope
* message body
* Returns: T on success, NIL on failure
*/
long smtp_mail (SENDSTREAM *stream,char *type,ENVELOPE *env,BODY *body)
{
/* Note: This assumes that the envelope will never generate a header of
* more than 8K. If your client generates godzilla headers, you will
* need to install your own rfc822out_t routine via SET_RFC822OUTPUT
* to use in place of this.
*/
char tmp[8*MAILTMPLEN];
long error = NIL;
if (!(env->to || env->cc || env->bcc)) {
/* no recipients in request */
smtp_fake (stream,SMTPHARDERROR,"No recipients specified");
return NIL;
}
smtp_send (stream,"RSET",NIL);/* make sure stream is in good shape */
strcpy (tmp,"FROM:<"); /* compose "MAIL FROM:<return-path>" */
if (env->return_path && env->return_path->host &&
!((env->return_path->adl &&
(strlen (env->return_path->adl) > SMTPMAXPATH)) ||
(strlen (env->return_path->mailbox) > SMTPMAXLOCALPART) ||
(strlen (env->return_path->host) > SMTPMAXDOMAIN)))
rfc822_address (tmp,env->return_path);
strcat (tmp,">");
if (ESMTP.ok) {
if (ESMTP.eightbit.ok && ESMTP.eightbit.want) strcat(tmp," BODY=8BITMIME");
if (ESMTP.dsn.ok && ESMTP.dsn.want)
strcat (tmp,ESMTP.dsn.full ? " RET=FULL" : " RET=HDRS");
}
/* send "MAIL FROM" command */
if (!(smtp_send (stream,type,tmp) == SMTPOK)) return NIL;
/* negotiate the recipients */
if (env->to) smtp_rcpt (stream,env->to,&error);
if (env->cc) smtp_rcpt (stream,env->cc,&error);
if (env->bcc) smtp_rcpt (stream,env->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;
}
/* 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!");
/* output data, return success status */
return rfc822_output (tmp,env,body,smtp_soutr,stream->netstream,
ESMTP.eightbit.ok && ESMTP.eightbit.want) &&
(smtp_send (stream,".",NIL) == SMTPOK);
}
/* Internal routines */
/* Simple Mail Transfer Protocol send recipient
* Accepts: SMTP stream
* address list
* pointer to error flag
*/
void smtp_rcpt (SENDSTREAM *stream,ADDRESS *adr,long *error)
{
char *s,tmp[MAILTMPLEN];
while (adr) { /* for each address on the list */
/* clear any former error */
if (adr->error) fs_give ((void **) &adr->error);
if (adr->host) { /* ignore group syntax */
/* enforce SMTP limits to protect the buffer */
if (adr->adl && (strlen (adr->adl) > SMTPMAXPATH)) {
adr->error = cpystr ("501 Path too long");
*error = T;
}
else if (strlen (adr->mailbox) > MAXLOCALPART) {
adr->error = cpystr ("501 Recipient name too long");
*error = T;
}
if ((strlen (adr->host) > SMTPMAXDOMAIN)) {
adr->error = cpystr ("501 Recipient domain too long");
*error = T;
}
else {
strcpy (tmp,"TO:<"); /* compose "RCPT TO:<return-path>" */
rfc822_address (tmp,adr);
strcat (tmp,">");
/* want notifications */
if (ESMTP.ok && ESMTP.dsn.ok && ESMTP.dsn.want) {
/* yes, start with prefix */
strcat (tmp," NOTIFY=");
s = tmp + strlen (tmp);
if (ESMTP.dsn.notify.failure) strcat (s,"FAILURE,");
if (ESMTP.dsn.notify.delay) strcat (s,"DELAY,");
if (ESMTP.dsn.notify.success) strcat (s,"SUCCESS,");
/* tie off last comma */
if (*s) s[strlen (s) - 1] = '\0';
else strcat (tmp,"NEVER");
}
/* send "RCPT TO" command */
if (!(smtp_send (stream,"RCPT",tmp) == SMTPOK)) {
*error = T; /* note that an error occurred */
adr->error = cpystr (stream->reply);
}
}
}
adr = adr->next; /* do any subsequent recipients */
}
}
/* Simple Mail Transfer Protocol send command
* Accepts: SMTP stream
* text
* Returns: reply code
*/
long smtp_send (SENDSTREAM *stream,char *command,char *args)
{
long ret;
do ret = smtp_send_work (stream,command,args);
while (ESMTP.auth && smtp_send_auth (stream,ret));
return ret;
}
/* SMTP send command worker routine
* Accepts: SEND stream
* text
* Returns: reply code
*/
long smtp_send_work (SENDSTREAM *stream,char *command,char *args)
{
char tmp[MAILTMPLEN+64];
/* build the complete command */
if (args) sprintf (tmp,"%s %s",command,args);
else strcpy (tmp,command);
if (stream->debug) mm_dlog (tmp);
strcat (tmp,"\015\012");
/* send the command */
if (!net_soutr (stream->netstream,tmp))
return smtp_fake (stream,SMTPSOFTFATAL,"SMTP connection broken (command)");
do stream->replycode = smtp_reply (stream);
while ((stream->replycode < 100) || (stream->reply[3] == '-'));
return stream->replycode;
}
/* SMTP send authentication if needed
* Accepts: SEND stream
* code from previous command
* Returns: T if need to redo command, NIL otherwise
*/
long smtp_send_auth (SENDSTREAM *stream,long code)
{
NETMBX mb;
char tmp[MAILTMPLEN];
switch (code) {
case SMTPWANTAUTH: case SMTPWANTAUTH2:
sprintf (tmp,"{%s/smtp}<none>",net_host (stream->netstream));
mail_valid_net_parse (tmp,&mb);
return smtp_auth (stream,&mb,tmp);
}
return NIL; /* no auth needed */
}
/* Simple Mail Transfer Protocol get reply
* Accepts: SMTP stream
* Returns: reply code
*/
long smtp_reply (SENDSTREAM *stream)
{
smtpverbose_t pv = (smtpverbose_t) mail_parameters (NIL,GET_SMTPVERBOSE,NIL);
long reply;
/* flush old reply */
if (stream->reply) fs_give ((void **) &stream->reply);
/* get reply */
if (!(stream->reply = net_getline (stream->netstream)))
return smtp_fake (stream,SMTPSOFTFATAL,"SMTP connection went away!");
if (stream->debug) mm_dlog (stream->reply);
reply = atol (stream->reply); /* return response code */
if (pv && (reply < 100)) (*pv) (stream->reply);
return reply;
}
/* Simple Mail Transfer Protocol send EHLO
* Accepts: SMTP stream
* host name to use in EHLO
* NETMBX structure
* Returns: reply code
*/
long smtp_ehlo (SENDSTREAM *stream,char *host,NETMBX *mb)
{
unsigned long i;
unsigned int j;
char *s,tmp[MAILTMPLEN];
sprintf (tmp,"EHLO %s",host); /* build the complete command */
if (stream->debug) mm_dlog (tmp);
strcat (tmp,"\015\012");
/* send the command */
if (!net_soutr (stream->netstream,tmp))
return smtp_fake (stream,SMTPSOFTFATAL,"SMTP connection broken (EHLO)");
/* got an OK reply? */
do if ((i = smtp_reply (stream)) == SMTPOK) {
ucase (strncpy (tmp,stream->reply+4,MAILTMPLEN-1));
tmp[MAILTMPLEN-1] = '\0';
/* note EHLO options */
if ((tmp[0] == '8') && (tmp[1] == 'B') && (tmp[2] == 'I') &&
(tmp[3] == 'T') && (tmp[4] == 'M') && (tmp[5] == 'I') &&
(tmp[6] == 'M') && (tmp[7] == 'E') && !tmp[8]) ESMTP.eightbit.ok = T;
else if ((tmp[0] == 'S') && (tmp[1] == 'I') && (tmp[2] == 'Z') &&
(tmp[3] == 'E') && (!tmp[4] || tmp[4] == ' ')) {
if (tmp[4]) ESMTP.size.limit = atoi (tmp+5);
ESMTP.size.ok = T;
}
else if ((tmp[0] == 'A') && (tmp[1] == 'U') && (tmp[2] == 'T') &&
(tmp[3] == 'H') && ((tmp[4] == ' ') || (tmp[4] == '='))) {
for (s = strtok (tmp+5," "); s && *s; s = strtok (NIL," "))
if ((j = mail_lookup_auth_name (s,mb->secflag)) &&
(--j < (8 * sizeof (ESMTP.auth)))) ESMTP.auth |= (1 << j);
}
else if ((tmp[0] == 'D') && (tmp[1] == 'S') && (tmp[2] == 'N') && !tmp[3])
ESMTP.dsn.ok = T;
else if ((tmp[0] == 'S') && (tmp[1] == 'E') && (tmp[2] == 'N') &&
(tmp[3] == 'D') && !tmp[4]) ESMTP.service.send = T;
else if ((tmp[0] == 'S') && (tmp[1] == 'O') && (tmp[2] == 'M') &&
(tmp[3] == 'L') && !tmp[4]) ESMTP.service.soml = T;
else if ((tmp[0] == 'S') && (tmp[1] == 'A') && (tmp[2] == 'M') &&
(tmp[3] == 'L') && !tmp[4]) ESMTP.service.saml = T;
else if ((tmp[0] == 'E') && (tmp[1] == 'X') && (tmp[2] == 'P') &&
(tmp[3] == 'N') && !tmp[4]) ESMTP.service.expn = T;
else if ((tmp[0] == 'H') && (tmp[1] == 'E') && (tmp[2] == 'L') &&
(tmp[3] == 'P') && !tmp[4]) ESMTP.service.help = T;
else if ((tmp[0] == 'T') && (tmp[1] == 'U') && (tmp[2] == 'R') &&
(tmp[3] == 'N') && !tmp[4]) ESMTP.service.turn = T;
else if ((tmp[0] == 'E') && (tmp[1] == 'T') && (tmp[2] == 'R') &&
(tmp[3] == 'N') && !tmp[4]) ESMTP.service.etrn = T;
else if ((tmp[0] == 'R') && (tmp[1] == 'E') && (tmp[2] == 'L') &&
(tmp[3] == 'A') && (tmp[4] == 'Y') && !tmp[5])
ESMTP.service.relay = T;
else if ((tmp[0] == 'P') && (tmp[1] == 'I') && (tmp[2] == 'P') &&
(tmp[3] == 'E') && (tmp[4] == 'L') && (tmp[5] == 'I') &&
(tmp[6] == 'N') && (tmp[7] == 'I') && (tmp[8] == 'N') &&
(tmp[9] == 'G') && !tmp[10]) ESMTP.service.pipe = T;
else if ((tmp[0] == 'E') && (tmp[1] == 'N') && (tmp[2] == 'H') &&
(tmp[3] == 'A') && (tmp[4] == 'N') && (tmp[5] == 'C') &&
(tmp[6] == 'E') && (tmp[7] == 'D') && (tmp[8] == 'S') &&
(tmp[9] == 'T') && (tmp[10] == 'A') && (tmp[11] == 'T') &&
(tmp[12] == 'U') && (tmp[13] == 'S') && (tmp[14] == 'C') &&
(tmp[15] == 'O') && (tmp[16] == 'D') && (tmp[17] == 'E') &&
(tmp[18] == 'S') && !tmp[19]) ESMTP.service.ensc = T;
}
while ((i < 100) || (stream->reply[3] == '-'));
return i; /* return the response code */
}
/* Simple Mail Transfer Protocol set fake error
* Accepts: SMTP stream
* SMTP error code
* error text
* Returns: error code
*/
long smtp_fake (SENDSTREAM *stream,long code,char *text)
{
/* flush any old reply */
if (stream->reply ) fs_give ((void **) &stream->reply);
/* set up pseudo-reply string */
stream->reply = (char *) fs_get (20+strlen (text));
sprintf (stream->reply,"%ld %s",code,text);
return code; /* return error code */
}
/* Simple Mail Transfer Protocol filter mail
* Accepts: stream
* string
* Returns: T on success, NIL on failure
*/
long smtp_soutr (void *stream,char *s)
{
char c,*t;
/* "." on first line */
if (s[0] == '.') net_soutr (stream,".");
/* find lines beginning with a "." */
while (t = strstr (s,"\015\012.")) {
c = *(t += 3); /* remember next character after "." */
*t = '\0'; /* tie off string */
/* output prefix */
if (!net_soutr (stream,s)) return NIL;
*t = c; /* restore delimiter */
s = t - 1; /* push pointer up to the "." */
}
/* output remainder of text */
return *s ? net_soutr (stream,s) : T;
}