* Copyright (c) 1983 Eric P. Allman
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
* %sccs.include.redist.c%
static char sccsid
[] = "@(#)usersmtp.c 8.41 (Berkeley) %G% (with SMTP)";
static char sccsid
[] = "@(#)usersmtp.c 8.41 (Berkeley) %G% (without SMTP)";
** USERSMTP -- run SMTP protocol from the user end.
** This protocol is described in RFC821.
#define REPLYTYPE(r) ((r) / 100) /* first digit of reply code */
#define REPLYCLASS(r) (((r) / 10) % 10) /* second digit of reply code */
#define SMTPCLOSING 421 /* "Service Shutting Down" */
char SmtpMsgBuffer
[MAXLINE
]; /* buffer for commands */
char SmtpReplyBuffer
[MAXLINE
]; /* buffer for replies */
char SmtpError
[MAXLINE
] = ""; /* save failure error messages */
int SmtpPid
; /* pid of mailer */
bool SmtpNeedIntro
; /* need "while talking" in transcript */
extern smtpmessage(char *f
, MAILER
*m
, MCI
*mci
, ...);
** SMTPINIT -- initialize SMTP.
** Opens the connection and sends the initial protocol.
** m -- mailer to create connection to.
** pvp -- pointer to parameter vector to pass to
** creates connection and sends initial protocol.
extern void esmtp_check();
extern void helo_options();
** Open the connection to the mailer.
CurHostName
= mci
->mci_host
; /* XXX UGLY XXX */
CurHostName
= MyHostName
;
/* need to clear old information */
syserr("451 smtpinit: state CLOSED");
mci
->mci_state
= MCIS_OPENING
;
** Get the greeting message.
** This should appear spontaneously. Give it five minutes to
SmtpPhase
= mci
->mci_phase
= "client greeting";
setproctitle("%s %s: %s", e
->e_id
, CurHostName
, mci
->mci_phase
);
r
= reply(m
, mci
, e
, TimeOuts
.to_initial
, esmtp_check
);
if (r
< 0 || REPLYTYPE(r
) == 4)
** Send the HELO command.
** My mother taught me to always introduce myself.
if (bitnset(M_ESMTP
, m
->m_flags
))
mci
->mci_flags
|= MCIF_ESMTP
;
if (bitset(MCIF_ESMTP
, mci
->mci_flags
))
smtpmessage("EHLO %s", m
, mci
, MyHostName
);
SmtpPhase
= mci
->mci_phase
= "client EHLO";
smtpmessage("HELO %s", m
, mci
, MyHostName
);
SmtpPhase
= mci
->mci_phase
= "client HELO";
setproctitle("%s %s: %s", e
->e_id
, CurHostName
, mci
->mci_phase
);
r
= reply(m
, mci
, e
, TimeOuts
.to_helo
, helo_options
);
else if (REPLYTYPE(r
) == 5)
if (bitset(MCIF_ESMTP
, mci
->mci_flags
))
/* try old SMTP instead */
mci
->mci_flags
&= ~MCIF_ESMTP
;
else if (REPLYTYPE(r
) != 2)
** Check to see if we actually ended up talking to ourself.
** This means we didn't know about an alias or MX, or we managed
** to connect to an echo server.
p
= strchr(&SmtpReplyBuffer
[4], ' ');
if (!bitnset(M_NOLOOPCHECK
, m
->m_flags
) &&
strcasecmp(&SmtpReplyBuffer
[4], MyHostName
) == 0)
syserr("553 %s config error: mail loops back to myself",
mci
->mci_exitstat
= EX_CONFIG
;
** If this is expected to be another sendmail, send some internal
if (bitnset(M_INTERNAL
, m
->m_flags
))
/* tell it to be verbose */
smtpmessage("VERB", m
, mci
);
r
= reply(m
, mci
, e
, TimeOuts
.to_miscshort
, NULL
);
if (mci
->mci_state
!= MCIS_CLOSED
)
mci
->mci_state
= MCIS_OPEN
;
/* got a 421 error code during startup */
mci
->mci_exitstat
= EX_TEMPFAIL
;
if (mci
->mci_state
!= MCIS_CLOSED
)
mci
->mci_exitstat
= EX_UNAVAILABLE
;
** ESMTP_CHECK -- check to see if this implementation likes ESMTP protocol
** line -- the response line.
** firstline -- set if this is the first line of the reply.
** mci -- the mailer connection info.
esmtp_check(line
, firstline
, m
, mci
, e
)
for (l
= line
; (l
= strchr(++l
, 'E')) != NULL
; )
if (strncmp(l
, "ESMTP ", 6) == 0)
mci
->mci_flags
|= MCIF_ESMTP
;
for (l
= line
; (l
= strchr(++l
, '8')) != NULL
; )
if (strncmp(l
, "8BIT OK", 7) == 0)
mci
->mci_flags
|= MCIF_8BITOK
;
** HELO_OPTIONS -- process the options on a HELO line.
** line -- the response line.
** firstline -- set if this is the first line of the reply.
** mci -- the mailer connection info.
helo_options(line
, firstline
, m
, mci
, e
)
if (strcasecmp(line
, "size") == 0)
mci
->mci_flags
|= MCIF_SIZE
;
mci
->mci_maxsize
= atol(p
);
else if (strcasecmp(line
, "8bitmime") == 0)
mci
->mci_flags
|= MCIF_8BITMIME
;
mci
->mci_flags
&= ~MCIF_7BIT
;
else if (strcasecmp(line
, "expn") == 0)
mci
->mci_flags
|= MCIF_EXPN
;
else if (strcasecmp(line
, "x-dsn-3") == 0)
mci
->mci_flags
|= MCIF_DSN
;
** SMTPMAILFROM -- send MAIL command
** mci -- the mailer connection structure.
** e -- the envelope (including the sender to specify).
printf("smtpmailfrom: CurHost=%s\n", CurHostName
);
/* set up appropriate options to include */
if (bitset(MCIF_SIZE
, mci
->mci_flags
) && e
->e_msgsize
> 0)
sprintf(optbuf
, " SIZE=%ld", e
->e_msgsize
);
bodytype
= e
->e_bodytype
;
if (bitset(MCIF_8BITMIME
, mci
->mci_flags
))
bitset(MM_MIME8BIT
, MimeMode
) &&
bitset(EF_HAS8BIT
, e
->e_flags
) &&
!bitnset(M_8BITS
, m
->m_flags
))
strcat(optbuf
, " BODY=");
strcat(optbuf
, bodytype
);
else if (bitnset(M_8BITS
, m
->m_flags
) ||
!bitset(EF_HAS8BIT
, e
->e_flags
) ||
(e
->e_bodytype
!= NULL
&&
strcasecmp(e
->e_bodytype
, "7bit") == 0))
/* just pass it through */
else if (bitset(MM_CVTMIME
, MimeMode
) &&
(e
->e_bodytype
!= NULL
|| !bitset(MM_PASS8BIT
, MimeMode
)))
/* must convert from 8bit MIME format to 7bit encoded */
mci
->mci_flags
|= MCIF_CVT8TO7
;
else if (!bitset(MM_PASS8BIT
, MimeMode
))
/* cannot just send a 8-bit version */
usrerr("%s does not support 8BITMIME", mci
->mci_host
);
if (bitset(MCIF_DSN
, mci
->mci_flags
))
strcat(optbuf
, " ENVID=");
strcat(optbuf
, e
->e_envid
);
if (bitset(EF_RET_PARAM
, e
->e_flags
))
if (bitset(EF_NO_BODY_RETN
, e
->e_flags
))
** Send the HOPS command.
** This is non-standard and may give an "unknown command".
** It can give a "bad hop count" error if the hop
** Send the MAIL command.
** Designates the sender.
mci
->mci_state
= MCIS_ACTIVE
;
if (bitset(EF_RESPONSE
, e
->e_flags
) &&
!bitnset(M_NO_NULL_FROM
, m
->m_flags
))
expand("\201g", buf
, sizeof buf
, e
);
/* strip off <angle brackets> (put back on below) */
bufp
= &buf
[strlen(buf
) - 1];
if (bitnset(M_LOCALMAILER
, e
->e_from
.q_mailer
->m_flags
) ||
!bitnset(M_FROMPATH
, m
->m_flags
))
smtpmessage("MAIL From:<%s>%s", m
, mci
, bufp
, optbuf
);
smtpmessage("MAIL From:<@%s%c%s>%s", m
, mci
, MyHostName
,
*bufp
== '@' ? ',' : ':', bufp
, optbuf
);
SmtpPhase
= mci
->mci_phase
= "client MAIL";
setproctitle("%s %s: %s", e
->e_id
, CurHostName
, mci
->mci_phase
);
r
= reply(m
, mci
, e
, TimeOuts
.to_mail
, NULL
);
if (r
< 0 || REPLYTYPE(r
) == 4)
mci
->mci_exitstat
= EX_TEMPFAIL
;
else if (r
== 501 || r
== 553)
/* syntax error in arguments */
/* signal service unavailable */
syslog(LOG_CRIT
, "%s: %s: SMTP MAIL protocol error: %s",
e
->e_id
, mci
->mci_host
, SmtpReplyBuffer
);
/* protocol error -- close up */
** SMTPRCPT -- designate recipient.
** to -- address of recipient.
** m -- the mailer we are sending to.
** mci -- the connection info for this transaction.
** e -- the envelope for this transaction.
** exit status corresponding to recipient status.
** Sends the mail via SMTP.
if (bitset(MCIF_DSN
, mci
->mci_flags
))
if (bitset(QHASNOTIFY
, to
->q_flags
) &&
bitset(QPRIMARY
, to
->q_flags
))
strcat(optbuf
, " NOTIFY=");
if (bitset(QPINGONSUCCESS
, to
->q_flags
))
strcat(optbuf
, "SUCCESS");
if (bitset(QPINGONFAILURE
, to
->q_flags
))
strcat(optbuf
, "FAILURE");
if (bitset(QPINGONDELAY
, to
->q_flags
))
strcat(optbuf
, " ORCPT=");
strcat(optbuf
, to
->q_orcpt
);
smtpmessage("RCPT To:<%s>%s", m
, mci
, to
->q_user
, optbuf
);
SmtpPhase
= mci
->mci_phase
= "client RCPT";
setproctitle("%s %s: %s", e
->e_id
, CurHostName
, mci
->mci_phase
);
r
= reply(m
, mci
, e
, TimeOuts
.to_rcpt
, NULL
);
setstatus(to
, SmtpReplyBuffer
);
if (r
< 0 || REPLYTYPE(r
) == 4)
else if (REPLYTYPE(r
) == 2)
else if (r
== 550 || r
== 551 || r
== 553)
else if (r
== 552 || r
== 554)
syslog(LOG_CRIT
, "%s: %s: SMTP RCPT protocol error: %s",
e
->e_id
, mci
->mci_host
, SmtpReplyBuffer
);
** SMTPDATA -- send the data and clean up the transaction.
** m -- mailer being sent to.
** e -- the envelope for this message.
** exit status corresponding to DATA command.
static jmp_buf CtxDataTimeout
;
static void datatimeout();
** First send the command and check that it is ok.
** Follow it up with a dot to terminate.
** Finally get the results of the transaction.
/* send the command and check ok to proceed */
smtpmessage("DATA", m
, mci
);
SmtpPhase
= mci
->mci_phase
= "client DATA 354";
setproctitle("%s %s: %s", e
->e_id
, CurHostName
, mci
->mci_phase
);
r
= reply(m
, mci
, e
, TimeOuts
.to_datainit
, NULL
);
if (r
< 0 || REPLYTYPE(r
) == 4)
syslog(LOG_CRIT
, "%s: %s: SMTP DATA-1 protocol error: %s",
e
->e_id
, mci
->mci_host
, SmtpReplyBuffer
);
** Set timeout around data writes. Make it at least large
** enough for DNS timeouts on all recipients plus some fudge
** factor. The main thing is that it should not be infinite.
if (setjmp(CtxDataTimeout
) != 0)
mci
->mci_exitstat
= EX_TEMPFAIL
;
mci
->mci_state
= MCIS_ERROR
;
syserr("451 timeout writing message to %s", mci
->mci_host
);
timeout
= e
->e_msgsize
/ 16;
if (timeout
< (time_t) 60)
timeout
+= e
->e_nrcpts
* 90;
ev
= setevent(timeout
, datatimeout
, 0);
** Output the actual message.
(*e
->e_puthdr
)(mci
, e
->e_header
, e
);
(*e
->e_putbody
)(mci
, e
, NULL
);
** Cleanup after sending message.
if (ferror(mci
->mci_out
))
/* error during processing -- don't send the dot */
mci
->mci_exitstat
= EX_IOERR
;
mci
->mci_state
= MCIS_ERROR
;
/* terminate the message */
fprintf(mci
->mci_out
, ".%s", m
->m_eol
);
if (TrafficLogFile
!= NULL
)
fprintf(TrafficLogFile
, "%05d >>> .\n", getpid());
/* check for the results of the transaction */
SmtpPhase
= mci
->mci_phase
= "client DATA 250";
setproctitle("%s %s: %s", e
->e_id
, CurHostName
, mci
->mci_phase
);
r
= reply(m
, mci
, e
, TimeOuts
.to_datafinal
, NULL
);
mci
->mci_state
= MCIS_OPEN
;
e
->e_statmsg
= newstr(&SmtpReplyBuffer
[4]);
else if (r
== 552 || r
== 554)
syslog(LOG_CRIT
, "%s: %s: SMTP DATA-2 protocol error: %s",
e
->e_id
, mci
->mci_host
, SmtpReplyBuffer
);
longjmp(CtxDataTimeout
, 1);
** SMTPQUIT -- close the SMTP connection.
** m -- a pointer to the mailer.
** sends the final protocol and closes the connection.
bool oldSuprErrs
= SuprErrs
;
** Suppress errors here -- we may be processing a different
** job when we do the quit connection, and we don't want the
** new job to be penalized for something that isn't it's
/* send the quit message if we haven't gotten I/O error */
if (mci
->mci_state
!= MCIS_ERROR
)
SmtpPhase
= "client QUIT";
smtpmessage("QUIT", m
, mci
);
(void) reply(m
, mci
, e
, TimeOuts
.to_quit
, NULL
);
if (mci
->mci_state
== MCIS_CLOSED
)
/* now actually close the connection and pick up the zombie */
(void) endmailer(mci
, e
, NULL
);
** SMTPRSET -- send a RSET (reset) command
SmtpPhase
= "client RSET";
smtpmessage("RSET", m
, mci
);
r
= reply(m
, mci
, e
, TimeOuts
.to_rset
, NULL
);
mci
->mci_state
= MCIS_ERROR
;
else if (REPLYTYPE(r
) == 2)
mci
->mci_state
= MCIS_OPEN
;
** SMTPPROBE -- check the connection state
MAILER
*m
= mci
->mci_mailer
;
extern ENVELOPE BlankEnvelope
;
ENVELOPE
*e
= &BlankEnvelope
;
SmtpPhase
= "client probe";
smtpmessage("RSET", m
, mci
);
r
= reply(m
, mci
, e
, TimeOuts
.to_miscshort
, NULL
);
if (r
< 0 || REPLYTYPE(r
) != 2)
** REPLY -- read arpanet reply
** m -- the mailer we are reading the reply from.
** mci -- the mailer connection info structure.
** e -- the current envelope.
** timeout -- the timeout for reads.
** pfunc -- processing function for second and subsequent
** lines of response -- if null, no special
** flushes the mail file.
reply(m
, mci
, e
, timeout
, pfunc
)
if (mci
->mci_out
!= NULL
)
(void) fflush(mci
->mci_out
);
** Read the input line, being careful not to hang.
for (bufp
= SmtpReplyBuffer
;; bufp
= junkbuf
)
/* actually do the read */
(void) fflush(e
->e_xfp
); /* for debugging */
/* if we are in the process of closing just give the code */
if (mci
->mci_state
== MCIS_CLOSED
)
if (mci
->mci_out
!= NULL
)
/* get the line from the other side */
p
= sfgets(bufp
, MAXLINE
, mci
->mci_in
, timeout
, SmtpPhase
);
mci
->mci_lastuse
= curtime();
extern char MsgBuf
[]; /* err.c */
/* if the remote end closed early, fake an error */
mci
->mci_exitstat
= EX_TEMPFAIL
;
usrerr("451 reply: read error from %s", mci
->mci_host
);
/* if debugging, pause so we can see state */
mci
->mci_state
= MCIS_ERROR
;
sprintf(p
, "%s... ", e
->e_to
);
sprintf(p
, "reply(%s) during %s",
mci
->mci_host
, SmtpPhase
);
/* EHLO failure is not a real error */
if (e
->e_xfp
!= NULL
&& (bufp
[0] == '4' ||
(bufp
[0] == '5' && strncmp(SmtpMsgBuffer
, "EHLO", 4) != 0)))
/* serious error -- log the previous command */
/* inform user who we are chatting with */
"... while talking to %s:\n",
if (SmtpMsgBuffer
[0] != '\0')
fprintf(e
->e_xfp
, ">>> %s\n", SmtpMsgBuffer
);
/* now log the message as from the other side */
fprintf(e
->e_xfp
, "<<< %s\n", bufp
);
/* display the input for verbose mode */
nmessage("050 %s", bufp
);
(*pfunc
)(bufp
, firstline
, m
, mci
, e
);
/* if continuation is required, we can go on */
/* ignore improperly formated input */
if (!(isascii(bufp
[0]) && isdigit(bufp
[0])))
/* decode the reply code */
/* extra semantics: 0xx codes are "informational" */
** Now look at SmtpReplyBuffer -- only care about the first
** line of the response from here on out.
/* save temporary failure messages for posterity */
if (SmtpReplyBuffer
[0] == '4' && SmtpError
[0] == '\0')
(void) strcpy(SmtpError
, SmtpReplyBuffer
);
/* reply code 421 is "Service Shutting Down" */
if (r
== SMTPCLOSING
&& mci
->mci_state
!= MCIS_SSD
)
/* send the quit protocol */
mci
->mci_state
= MCIS_SSD
;
** SMTPMESSAGE -- send message to server
** m -- the mailer to control formatting.
** writes message to mci->mci_out.
smtpmessage(char *f
, MAILER
*m
, MCI
*mci
, ...)
smtpmessage(f
, m
, mci
, va_alist
)
(void) vsprintf(SmtpMsgBuffer
, f
, ap
);
if (tTd(18, 1) || Verbose
)
nmessage(">>> %s", SmtpMsgBuffer
);
if (TrafficLogFile
!= NULL
)
fprintf(TrafficLogFile
, "%05d >>> %s\n", getpid(), SmtpMsgBuffer
);
if (mci
->mci_out
!= NULL
)
fprintf(mci
->mci_out
, "%s%s", SmtpMsgBuffer
,
m
== NULL
? "\r\n" : m
->m_eol
);
printf("smtpmessage: NULL mci_out\n");