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.