ftp.nice.ch/pub/next/unix/mail/smail3.1.20.s.tar.gz#/smail3.1.20/src/transports/smtplib.c

This is smtplib.c in view mode; [Download] [Up]

/* @(#)src/transports/smtplib.c	1.3 02 Dec 1990 06:08:25 */

/*
 *    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();


/*
 * 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;
}


/*
 * 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;
}


/*
 * 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);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.