|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T s
Length: 20914 (0x51b2)
Types: TextFile
Names: »smtplib.c«
└─⟦9ae75bfbd⟧ Bits:30007242 EUUGD3: Starter Kit
└─⟦2fafebccf⟧ »EurOpenD3/mail/smail3.1.19.tar.Z«
└─⟦bcd2bc73f⟧
└─⟦this⟧ »src/transports/smtplib.c«
/* @(#)smtplib.c 1.4 3/25/89 15:34:46 */
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*/
/*
* smtplib.c:
* Send mail using the SMTP protocol. This soure file is a set
* of library routines that can be used by transport drivers that
* can handle creating the virtual circuit connections.
*/
#include <stdio.h>
#include <signal.h>
#include <ctype.h>
#include <setjmp.h>
#include "defs.h"
#include "../smail.h"
#include "../dys.h"
#include "../addr.h"
#include "../transport.h"
#include "../spool.h"
#include "smtplib.h"
#ifndef DEPEND
# include "../extern.h"
# include "../error.h"
# include "../debug.h"
#endif
/* supported SMTP commands */
#define HELO(domain) "HELO %s", domain
#define MAIL_BEGIN "MAIL FROM:<"
#define MAIL_END ">"
#define RCPT_BEGIN "RCPT TO:<"
#define RCPT_END ">"
#define DATA "DATA"
#define DATA_END "."
#define QUIT "QUIT"
/* reply code groups, encoded in hex */
#define POSITIVE_PRELIM 0x100 /* positive preliminary replies */
#define POSITIVE_COMPLETE 0x200 /* positive completion replies */
#define POSITIVE_INTERMEDIATE 0x300 /* positive intermediate replies */
#define NEGATIVE_TRY_AGAIN 0x400 /* transient negative completion */
#define NEGATIVE_FAILED 0x500 /* permanent negative completion */
#define REPLY_GROUP(c) ((c)&0xf00) /* mask out reply code group */
/* specific reply codes */
#define REPLY_READY 0x220 /* SMTP service ready */
#define REPLY_FINISHED 0x221 /* SMTP service finished, closing */
#define REPLY_OK 0x250 /* successful command completion */
#define REPLY_WILLFORWARD 0x251 /* user address will be forwarded */
#define REPLY_START_DATA 0x354 /* okay to start sending message */
#define REPLY_DOWN 0x421 /* remote SMTP closing down */
#define REPLY_STORAGE_FULL 0x552 /* remote storage full */
/* pseudo-reply codes */
#define REPLY_PROTO_ERROR 0x498 /* protocol error on read */
#define REPLY_TIMEOUT 0x499 /* timeout on read, or EOF on read */
/* variables local to this file */
static struct str smtp_out; /* region for outgoing commands */
static struct str smtp_in; /* region from incoming responses */
int smtp_init_flag = FALSE; /* TRUE if SMTP system initialized */
static jmp_buf timeout_buf; /* timeouts jump here */
/* functions local to this file */
static void do_smtp_shutdown();
static int wait_write_command();
static int wait_read_response();
static void catch_timeout();
static struct error *no_remote();
static struct error *try_again();
static struct error *fatal_error();
static struct error *remote_full();
static struct error *remote_bad_address();
static struct error *write_failed();
static struct error *read_failed();
\f
/*
* smtp_startup - initiate contact on an SMTP connection
*
* given input and output channels to a remote SMTP process, initiate
* the session for future mail commands. Once the startup has been
* acomplished, smtp_send() can be used to send individual messages.
*
* return:
* SMTP_SUCCEED on successful startup
* SMTP_FAIL if the connection should not be retried
* SMTP_AGAIN if the connection should be retried later
*
* For SMTP_FAIL and SMTP_AGAIN, return a filled-in error structure.
*/
int
smtp_startup(smtpb, error_p)
struct smtp *smtpb; /* SMTP description block */
struct error **error_p; /* error description */
{
int reply;
char *reply_text;
if (! smtp_init_flag) {
STR_INIT(&smtp_in);
STR_INIT(&smtp_out);
smtp_init_flag = TRUE;
}
/*
* wait for the sender to say he is ready.
* Possible reponses:
* 220 - service ready (continue conversation)
* 421 - closing down (try again later)
*/
if (smtpb->in) {
reply = wait_read_response(smtpb, smtpb->short_timeout, &reply_text);
}
if (reply != REPLY_READY) {
/* didn't get an OK reponse, try again later */
*error_p = no_remote(smtpb->tp, reply_text);
return SMTP_AGAIN;
}
/*
* say who we are.
* Possible responses:
* 250 - okay (continue conversation)
* 421 - closing down (try again later)
* 5xx - fatal error (return message to sender)
*/
smtp_out.i = 0;
(void) str_printf(&smtp_out, HELO(primary_name));
reply = wait_write_command(smtpb, smtpb->short_timeout,
smtp_out.p, smtp_out.i, &reply_text);
if (REPLY_GROUP(reply) == NEGATIVE_TRY_AGAIN) {
/* remote SMTP closed, try again later */
*error_p = try_again(smtpb->tp, reply_text);
return SMTP_AGAIN;
}
if (reply != REPLY_OK) {
/* fatal error, return message to sender */
*error_p = fatal_error(smtpb->tp, reply_text);
return SMTP_FAIL;
}
/* connection established */
return SMTP_SUCCEED;
}
\f
/*
* smtp_send - mail a message to a remote SMTP process
*
* Using a virtual circuit connection of some kind, transmit
* the current spooled message to the given set of recipient
* addresses. If the circuit has only a write channel, and no read
* channel, then transmit batch SMTP.
*
* Return:
* SUCCEED - more smtp messages can be sent.
* FAIL - don't try to send any more messages.
*/
int
smtp_send(smtpb, addr, succeed, defer, fail)
struct smtp *smtpb; /* SMTP description block */
struct addr *addr; /* list of recipient addresses */
struct addr **succeed; /* successful addresses */
struct addr **defer; /* addresses to be retried */
struct addr **fail; /* failed addresses */
{
register int reply; /* reply code from SMTP commands */
char *reply_text; /* text of reply */
int success; /* success value from calls */
struct addr *cur; /* current address being sent */
struct addr *next; /* next addr to send */
struct addr *okay; /* partially successful addrs */
char *error_text;
if (! smtp_init_flag) {
STR_INIT(&smtp_in);
STR_INIT(&smtp_out);
smtp_init_flag = TRUE;
}
/*
* signal that we are sending a new message,
* and give the sender address.
*
* Possible responses:
* 250 - okay (continue conversation)
* 4xx - temporary error (try again later)
* 5xx - fatal error (return message to sender)
*/
/* the sender address should be sent as a route-addr back to the sender */
smtp_out.i = 0;
STR_CAT(&smtp_out, MAIL_BEGIN);
if ((smtpb->tp->flags & LOCAL_TPORT) == 0) {
char *target;
char *remainder;
char *copy_sender = COPY_STRING(sender);
success = parse_address(copy_sender, &target, &remainder);
switch (success) {
case RFC_ROUTE:
case RFC_ENDROUTE:
if (islocalhost(target)) {
str_printf(&smtp_out, "@%s%c%s",
primary_name,
success == RFC_ROUTE? ',': ':',
remainder);
} else {
str_printf(&smtp_out, "@%s,%s", primary_name, sender);
}
break;
case MAILBOX:
if (islocalhost(target)) {
str_printf(&smtp_out, "@%s@%s", remainder, primary_name);
} else {
str_printf(&smtp_out, "@%s:%s", primary_name, sender);
}
break;
case UUCP_ROUTE:
case DECNET:
case BERKENET:
case PCT_MAILBOX:
if (islocalhost(target)) {
str_printf(&smtp_out, "@%s@%s", remainder, primary_name);
break;
}
/* FALL THROUGH */
case LOCAL:
str_printf(&smtp_out, "%s@%s", sender, primary_name);
break;
default:
STR_CAT(&smtp_out, sender);
break;
}
xfree(copy_sender);
} else {
STR_CAT(&smtp_out, sender);
}
STR_CAT(&smtp_out, MAIL_END);
reply = wait_write_command(smtpb, smtpb->long_timeout,
smtp_out.p, smtp_out.i, &reply_text);
if (REPLY_GROUP(reply) == NEGATIVE_TRY_AGAIN) {
insert_addr_list(addr, defer, try_again(smtpb->tp, reply_text));
do_smtp_shutdown(smtpb, reply);
return FAIL;
} else if (REPLY_GROUP(reply) == NEGATIVE_FAILED) {
insert_addr_list(addr, fail, fatal_error(smtpb->tp, reply_text));
do_smtp_shutdown(smtpb, reply);
return FAIL;
}
/* give all of the recipient addresses to the remote SMTP */
okay = NULL;
for (cur = addr; cur; cur = next) {
next = cur->succ;
/*
* each recipient specified individually.
* Possible responses:
* 250, 251 - okay, or forwarded (continue conversation)
* 451, 452 - remote error (try again later)
* 421 - connection closing (try again later)
* 5xx - failure (return message to sender)
*/
smtp_out.i = 0;
if (smtpb->tp->flags & LOCAL_TPORT || addr->next_host == NULL) {
(void) str_printf(&smtp_out, "%s%s%s",
RCPT_BEGIN, cur->next_addr, RCPT_END);
} else {
switch (parse_address(addr->next_addr, (char **)NULL,
&error_text))
{
case PARSE_ERROR:
case RFC_ROUTE:
case RFC_ENDROUTE:
case MAILBOX:
(void) str_printf(&smtp_out, "%s%s%s",
RCPT_BEGIN, cur->next_addr, RCPT_END);
break;
default:
(void) str_printf(&smtp_out, "%s%s@%s%s",
RCPT_BEGIN,
cur->next_addr,
cur->next_host,
RCPT_END);
break;
}
}
reply = wait_write_command(smtpb, smtpb->long_timeout,
smtp_out.p, smtp_out.i, &reply_text);
if (reply == REPLY_STORAGE_FULL) {
insert_addr_list(cur, defer, remote_full(smtpb->tp, reply_text));
next = NULL;
} else if (REPLY_GROUP(reply) == NEGATIVE_TRY_AGAIN) {
struct error *error;
error = try_again(smtpb->tp, reply_text);
insert_addr_list(cur, defer, error);
insert_addr_list(okay, defer, error);
do_smtp_shutdown(smtpb, reply);
return FAIL;
} else if (REPLY_GROUP(reply) == NEGATIVE_FAILED) {
cur->error = remote_bad_address(smtpb->tp, reply_text);
cur->succ = *fail;
*fail = cur;
} else {
/* successful thus far */
cur->succ = okay;
okay = cur;
}
}
/*
* say that we will next be sending the actual data
* Possible responses:
* 354 - go ahead with the message (continue conversation)
* 421 - closing connection (try all recipients again later)
* 4xx - remote error (try all recipients again later)
* 5xx - fatal error (return message to sender)
*/
reply = wait_write_command(smtpb, smtpb->long_timeout,
DATA, sizeof(DATA) - 1, &reply_text);
if (REPLY_GROUP(reply) == NEGATIVE_TRY_AGAIN) {
insert_addr_list(okay, defer, try_again(smtpb->tp, reply_text));
do_smtp_shutdown(smtpb, reply);
return FAIL;
}
if (reply != REPLY_START_DATA && reply != REPLY_OK) {
/* fatal error, return message to sender */
insert_addr_list(okay, fail, fatal_error(smtpb->tp, reply_text));
do_smtp_shutdown(smtpb, reply);
return FAIL;
}
/*
* send the message, using the hidden dot protocol.
*/
smtpb->tp->flags |= PUT_DOTS;
success = write_message(smtpb->out, smtpb->tp, (struct addr *)NULL);
if (success == WRITE_FAIL) {
insert_addr_list(okay, defer, write_failed(smtpb->tp));
/* assume connection has gone away */
return FAIL;
}
if (success == READ_FAIL) {
insert_addr_list(okay, defer, read_failed(smtpb->tp));
/* okay to advance to the next message */
return SUCCEED;
}
/* finish by sending the final "." */
reply = wait_write_command(smtpb, smtpb->long_timeout,
DATA_END, sizeof(DATA_END)-1, &reply_text);
if (REPLY_GROUP(reply) == NEGATIVE_TRY_AGAIN) {
insert_addr_list(okay, defer, try_again(smtpb->tp, reply_text));
do_smtp_shutdown(smtpb, reply);
return FAIL;
}
if (reply != REPLY_OK) {
/* fatal error, return message to sender */
insert_addr_list(okay, fail, fatal_error(smtpb->tp, reply_text));
do_smtp_shutdown(smtpb, reply);
return FAIL;
}
insert_addr_list(okay, succeed, (struct error *)NULL);
return SUCCEED;
}
\f
/*
* smtp_shutdown - end an interactive SMTP conversation
*/
void
smtp_shutdown(smtpb)
struct smtp *smtpb; /* SMTP description block */
{
char *reply_text; /* where to store reply text */
(void) wait_write_command(smtpb, smtpb->short_timeout,
QUIT, sizeof(QUIT)-1, &reply_text);
}
/*
* do_smtp_shutdown - call shutdown if the connection wasn't dropped
*/
static void
do_smtp_shutdown(smtpb, reply)
struct smtp *smtpb; /* SMTP description block */
int reply; /* response code from command */
{
switch (reply) {
case REPLY_DOWN:
case REPLY_PROTO_ERROR:
case REPLY_TIMEOUT:
break;
default:
smtp_shutdown(smtpb);
break;
}
}
/*
* wait_write_command - send a command, then wait for the response
*
* For batched SMTP, return a response code of REPLY_OK, unless there
* was a write error.
*/
static int
wait_write_command(smtpb, timeout, text, len, reply_text)
struct smtp *smtpb; /* SMTP description block */
unsigned timeout; /* read timeout */
char *text; /* text of command */
register int len; /* length of command */
char **reply_text; /* text of response from remote */
{
register FILE *f = smtpb->out;
register char *cp;
/* send out the command */
for (cp = text; len; cp++, --len) {
putc(*cp, f);
}
/* terminate the command line */
for (cp = smtpb->nl; *cp; cp++) {
putc(*cp, f);
}
(void) fflush(f);
if (ferror(f)) {
*reply_text = "499 write error, remote probably down\n";
return REPLY_TIMEOUT;
}
/* wait for the response to come back */
if (smtpb->in) {
return wait_read_response(smtpb, timeout, reply_text);
}
return REPLY_OK;
}
/*
* wait_read_response - wait for a response from the remote SMTP process
*
* return the response code, and the response text. Abort on timeouts
* or end-of-file or protocol errors.
*/
static int
wait_read_response(smtpb, timeout, reply_text)
struct smtp *smtpb; /* SMTP description block */
unsigned timeout; /* read timeout */
char **reply_text; /* return text of response here */
{
register int lstart; /* offset to start of an input line */
int success; /* success value from calls */
void (*save_sig)(); /* previous alarm signal catcher */
unsigned save_alarm; /* previous alarm value */
register int c;
/* reset the input buffer */
smtp_in.i = 0;
save_alarm = alarm((unsigned)0);
/* if we timeout, say so */
if (setjmp(timeout_buf)) {
(void) signal(SIGALRM, save_sig);
(void) alarm(save_alarm);
*reply_text = "499 timeout on read from remote SMTP process\n";
return REPLY_TIMEOUT;
}
/* don't let reads block forever */
save_sig = (void (*)())signal(SIGALRM, catch_timeout);
(void) alarm(timeout);
/* loop until the response is completed */
do {
lstart = smtp_in.i;
while ((c = getc(smtpb->in)) != EOF && c != '\n') {
STR_NEXT(&smtp_in, c);
}
if (smtp_in.p[smtp_in.i] == '\r') {
--smtp_in.i;
}
STR_NEXT(&smtp_in, '\n');
} while (c != EOF &&
isdigit(smtp_in.p[lstart]) &&
isdigit(smtp_in.p[lstart + 1]) &&
isdigit(smtp_in.p[lstart + 2]) &&
smtp_in.p[lstart + 3] == '-');
/* replace last newline with a nul byte */
smtp_in.p[smtp_in.i] = '\0';
/* restore previous alarm catcher and setting */
(void) alarm((unsigned)0);
(void) signal(SIGALRM, save_sig);
(void) alarm(save_alarm);
if (c == EOF) {
*reply_text = "499 read error from remote SMTP process";
return REPLY_TIMEOUT;
}
if (! isdigit(smtp_in.p[lstart]) ||
! isdigit(smtp_in.p[lstart + 1]) ||
! isdigit(smtp_in.p[lstart + 2]) ||
! isspace(smtp_in.p[lstart+3]))
{
*reply_text = "498 protocol error in reply from remote SMTP process";
return REPLY_PROTO_ERROR;
}
*reply_text = smtp_in.p;
(void) sscanf(*reply_text, "%3x", &success);
return success;
}
/* catch_timeout - longjmp after an alarm */
static void
catch_timeout()
{
longjmp(timeout_buf, 1);
}
static struct error *
no_remote(tp, reply_text)
struct transport *tp;
char *reply_text;
{
char *error_text;
/*
* ERR_172 - no connection to remote SMTP server
*
* DESCRIPTION
* No 220 reply was received from the remote SMTP server over
* the virtual circuit, assume that the remote host was not
* really reachable.
*
* ACTIONS
* Try again later.
*
* RESOLUTION
* Retries should eventually take care of the problem.
*/
error_text =
xprintf("transport %s: no connection to remote SMTP server: %s",
tp->name, reply_text);
DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
return note_error(ERR_172, error_text);
}
static struct error *
try_again(tp, reply_text)
struct transport *tp;
char *reply_text;
{
char *error_text;
/*
* ERR_151 - temporary SMTP error
*
* DESCRIPTION
* wait_write_command() received a temporary error response
* while conversing with the remote host. These can be read
* errors or read timeouts, or they can be negative responses
* from the remote side.
*
* ACTIONS
* Defer the input addresses.
*
* RESOLUTION
* Retries should eventually take care of the problem.
*/
error_text = xprintf("transport %s: %s", tp->name, reply_text);
DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
return note_error(ERR_151, error_text);
}
static struct error *
fatal_error(tp, reply_text)
struct transport *tp;
char *reply_text;
{
char *error_text;
/*
* ERR_152 - permanent SMTP error
*
* DESCRIPTION
* wait_write_command() received a permanent error response
* while conversing with the remote host. These are generally
* permanent negative response codes from the remote side.
*
* ACTIONS
* Fail the input addresses, and return to the address owner
* or the message originator.
*
* RESOLUTION
* The resolution depends upon the error message. See RFC822
* for details.
*/
error_text = xprintf("transport %s: %s", tp->name, reply_text);
DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
return note_error(ERR_152 | ERR_NSOWNER, error_text);
}
static struct error *
remote_full(tp, reply_text)
struct transport *tp;
char *reply_text;
{
char *error_text;
/*
* ERR_153 - Remote host's storage is full
*
* DESCRIPTION
* The remote host returned status indicating that he
* cannot take more recipient addresses.
*
* ACTIONS
* Defer the remaining addresses in hopes that other
* storage is not affected.
*
* RESOLUTION
* Hopefully by sending a few addresses now and a few later
* the message will eventually be delivered to all
* recipients.
*/
error_text = xprintf("transport %s: %s", tp->name, reply_text);
DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
return note_error(ERR_153, error_text);
}
static struct error *
remote_bad_address(tp, reply_text)
struct transport *tp;
char *reply_text;
{
char *error_text;
/*
* ERR_156 - remote host returned bad address status
*
* DESCRIPTION
* Status from the remote host indicates an error in the
* recipient address just sent to it.
*
* ACTIONS
* Return mail to the address owner or to the sender.
*
* RESOLUTION
* An alternate address should be attempted or the
* postmaster at the site that generated the error message
* should be consulted.
*/
error_text = xprintf("transport %s: %s", tp->name, reply_text);
DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
return note_error(ERR_156 | ERR_NSOWNER, error_text);
}
static struct error *
write_failed(tp)
struct transport *tp;
{
char *error_text;
/*
* ERR_154 - Error writing to remote host
*
* DESCRIPTION
* An error occured when transmitting the message text to the
* remote host.
*
* ACTIONS
* Defer all of the remaining input addresses.
*
* RESOLUTION
* Hopefully retries should take care of the problem.
*/
error_text = xprintf("transport %s: Error writing to remote host",
tp->name);
DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
return note_error(ERR_154, error_text);
}
static struct error *
read_failed(tp)
struct transport *tp;
{
char *error_text;
/*
* ERR_155 - Failed to read spooled message
*
* DESCRIPTION
* We failed to read the spooled message on our side while
* sending the message text to a remote host.
*
* ACTIONS
* Defer the message with a configuration error. If we are
* unable to read the spool file, there is little we can do.
*
* RESOLUTION
* "This should never happen".
*/
error_text = xprintf("transport %s: Error writing to remote host",
tp->name);
DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
return note_error(ERR_155 | ERR_CONFERR, error_text);
}