home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC-Online 1996 May
/
PCOnline_05_1996.bin
/
linux
/
source
/
contrib
/
smail
/
smail-3.1
/
smail-3
/
smail-3.1.28
/
src
/
smtprecv.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-09-06
|
19KB
|
762 lines
/* @(#)src/smtprecv.c 1.16 9/6/92 01:33:48 */
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
* Copyright (C) 1992 Ronald S. Karr
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*/
/*
* smtprecv.c:
* Receive mail using the SMTP protocol.
*/
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include "defs.h"
#include "main.h"
#include "smail.h"
#include "addr.h"
#include "dys.h"
#include "log.h"
#include "hash.h"
#ifndef DEPEND
# include "extern.h"
# include "debug.h"
# include "exitcodes.h"
#endif
/* library functions */
extern long time();
/* types local to this file */
enum e_smtp_commands {
HELO_CMD, /* HELO domain */
MAIL_CMD, /* MAIL FROM:<sender> */
RCPT_CMD, /* RCPT TO:<recipient> */
DATA_CMD, /* DATA */
VRFY_CMD, /* VRFY */
EXPN_CMD, /* EXPN */
QUIT_CMD, /* QUIT */
RSET_CMD, /* RSET */
NOOP_CMD, /* NOOP */
DEBUG_CMD, /* DEBUG [level] */
HELP_CMD, /* HELP */
EOF_CMD, /* end of file encountered */
OTHER_CMD, /* unknown command */
};
/* functions local to this file */
static void reset_state();
static enum e_smtp_commands read_smtp_command();
static void expand_addr();
static void verify_addr();
static void smtp_input_signals();
static void smtp_processing_signals();
static void set_term_signal();
static void smtp_receive_timeout_sig();
static void smtp_sig_unlink();
/* variables local to this file */
static char *data; /* interesting data within input */
static int term_signal;
static int smtp_remove_on_timeout;
static FILE *out_file;
static char *help_msg[] = {
"250-The following SMTP commands are recognized:",
"250-",
"250- HELO hostname - startup and give your hostname",
"250- MAIL FROM:<sender-address> - start transaction from sender",
"250- RCPT TO:<recipient-address> - name recipient for message",
"250- VRFY <address> - verify deliverability of address",
"250- EXPN <address> - expand mailing list address",
"250- DATA - start text of mail message",
"250- RSET - reset state, drop transaction",
"250- NOOP - do nothing",
"250- DEBUG [level] - set debugging level, default 1",
"250- HELP - produce this help message",
"250- QUIT - close SMTP connection",
"250-",
"250-The normal sequence of events in sending a message is to state the",
"250-sender address with a MAIL FROM command, give the recipients with",
"250-as many RCPT TO commands as are required (one address per command)",
"250-and then to specify the mail message text after the DATA command.",
"250 Multiple messages may be specified. End the last one with a QUIT."
};
/*
* receive_smtp - receive mail over SMTP.
*
* Take SMTP commands on the `in' file. Send reply messages
* to the `out' file. If `out' is NULL, then don't send reply
* messages (i.e., read batch SMTP commands).
*
* return an array of spool files which were created in this SMTP
* conversation.
*
* The last spooled message is left open as an efficiency move, so the
* caller must arrange to close it or process it to completion. As
* well, it is the callers responsibility to close the input and
* output channels.
*/
char **
receive_smtp(in, out)
FILE *in; /* stream of SMTP commands */
FILE *out; /* channel for responses */
{
char *error; /* temp to hold error messages */
struct addr *cur;
static char **files = NULL;
static int file_cnt = 7; /* initially put 7 parts in array */
int file_i = 0; /* index starts at the beginning */
/* save important state to restore after initialize_state() */
enum er_proc save_error_proc = error_processing;
int save_do_aliasing = do_aliasing;
int save_dont_deliver = dont_deliver;
FILE *save_errfile = errfile;
int save_debug = debug;
int temp, i;
/* initialize state */
initialize_state();
/* restore important state */
error_processing = save_error_proc;
do_aliasing = save_do_aliasing;
dont_deliver = save_dont_deliver;
term_signal = FALSE;
out_file = out;
smtp_processing_signals();
if (out) {
(void) signal(SIGALRM, smtp_receive_timeout_sig);
}
/* allocate an initial chunk of spool filename slots */
if (files == NULL) {
files = (char **)xmalloc((file_cnt + 1) * sizeof(*files));
}
/* output the startup banner line */
if (out) {
char *s;
s = expand_string(smtp_banner, (struct addr *)NULL,
(char *)NULL, (char *)NULL);
while (*s) {
fprintf(out, "220%c", index(s, '\n') == NULL? ' ': '-');
while (*s) {
putc(*s, out);
if (*s++ == '\n') break;
}
}
putc('\r', out);
putc('\n', out);
fflush(out);
}
while (! term_signal || out == NULL) {
if (out) {
alarm(smtp_receive_command_timeout);
}
switch (read_smtp_command(in)) {
case HELO_CMD:
strip_rfc822_comments(data);
if (out && data[0] == '\0') {
fprintf(out, "501 HELO requires domain name as operand\r\n");
fflush(out);
break;
}
if (sender_host == NULL && data[0] != '\0') {
sender_host = COPY_STRING(data);
}
if (sender_proto == NULL) {
sender_proto = (out? "smtp": "bsmtp");
}
if (out) {
fprintf(out, "250 %s Hello %s\r\n", primary_name, data);
fflush(out);
}
reset_state();
break;
case MAIL_CMD:
strip_rfc822_comments(data);
if (out && data[0] == '\0') {
fprintf(out, "501 MAIL FROM requires address as operand\r\n");
fflush(out);
break;
}
if (sender) {
if (out) {
fprintf(out, "503 Sender already specified\r\n");
fflush(out);
}
break;
}
sender = preparse_address(data, &error);
if (out) {
if (sender) {
fprintf(out, "250 <%s> ... Sender Okay\r\n",
sender);
} else {
fprintf(out, "501 <%s> ... %s\r\n", data, error);
}
fflush(out);
}
if (sender && sender[0] == '\0') {
/* special error sender form <> given */
sender = COPY_STRING("<>");
}
if (sender && EQ(sender, "+")) {
/* special smail-internal <+> was given */
sender = COPY_STRING("<+>");
}
break;
case RCPT_CMD:
strip_rfc822_comments(data);
if (out && data[0] == '\0') {
fprintf(out, "501 RCPT TO requires address as operand\r\n");
fflush(out);
break;
}
cur = alloc_addr();
if (out) {
if (cur->work_addr = preparse_address(data, &error)) {
fprintf(out, "250 <%s> ... Recipient Okay\r\n",
cur->work_addr);
fflush(out);
} else {
fprintf(out, "501 <%s> ... %s\r\n", data, error);
fflush(out);
break;
}
}
/*
* surround in angle brackets, if the addr begins with `-'.
* This will avoid ambiguities in the data dumped to the spool
* file.
*/
if (data[0] == '-') {
cur->in_addr = xprintf("<%s>", data);
} else {
cur->in_addr = COPY_STRING(data);
}
cur->succ = recipients;
recipients = cur;
break;
case DATA_CMD:
if (sender == NULL) {
if (out) {
fprintf(out, "503 Need MAIL command\r\n");
fflush(out);
} else {
/* sink the message for the sake of further batched cmds */
if (spool_fn) {
close_spool();
}
swallow_smtp(in);
}
break;
}
if (recipients == NULL) {
if (out) {
fprintf(out, "503 Need RCPT (recpient)\r\n");
fflush(out);
} else {
/* sink the message for the sake of further batched cmds */
if (spool_fn) {
close_spool();
}
swallow_smtp(in);
}
break;
}
if (out) {
fprintf(out,
"354 Enter mail, end with \".\" on a line by itself\r\n");
fflush(out);
alarm(0);
}
/*
* if we had the previous spool file opened, close it
* before creating a new one
*/
if (spool_fn) {
close_spool();
}
if (out) {
/*
* if we are not interactive and cannot send back failure
* messages, always try to accept the complete message.
*/
smtp_input_signals();
alarm(smtp_receive_message_timeout);
}
smtp_remove_on_timeout = 1;
if (queue_message(in, SMTP_DOTS, recipients, &error) == FAIL) {
exitvalue = EX_IOERR;
log_spool_errors();
if (out) {
fprintf(out, "451 Failed to queue message: %s: %s\r\n",
error, strerrno());
fflush(out);
break;
} else if (errfile) {
fprintf(errfile, "Failed to queue message: %s: %s\r\n",
error, strerrno());
}
}
smtp_processing_signals();
if (sender == NULL) {
unlink_spool();
reset_state();
break;
}
if (read_message() == NULL) {
log_spool_errors();
unlink_spool();
if (out) {
fprintf(out, "451 error in spooled message\r\n");
fflush(out);
}
break;
}
alarm(0);
smtp_remove_on_timeout = 0;
check_grade();
log_incoming();
log_spool_errors();
if (out) {
fprintf(out, "250 Mail accepted\r\n");
fflush(out);
}
/* always allow an extra element to store the ending NULL */
if (file_i >= file_cnt) {
/* we need to grow the array of spool file names */
file_cnt += 8;
files = (char **)xrealloc((char *)files,
(file_cnt + 1) * sizeof(*files));
}
files[file_i++] = xprintf("%s/input/%s", spool_dir, spool_fn);
reset_state();
break;
case VRFY_CMD:
if (out) {
#ifdef NO_VERIFY
fprintf(out, "502 Command not implemented\r\n");
#else
strip_rfc822_comments(data);
verify_addr(data, out);
fflush(out);
#endif
}
break;
case EXPN_CMD:
if (out) {
#ifdef NO_VERIFY
fprintf(out, "502 Command not implemented\r\n");
#else
strip_rfc822_comments(data);
expand_addr(data, out);
fflush(out);
#endif
}
break;
case QUIT_CMD:
if (out) {
fprintf(out, "221 %s closing connection\r\n",
primary_name);
fflush(out);
}
reset_state();
files[file_i++] = NULL;
errfile = save_errfile;
debug = save_debug;
return files;
case RSET_CMD:
reset_state();
if (out) {
fprintf(out, "250 Reset state\r\n");
fflush(out);
}
break;
case NOOP_CMD:
if (out) {
fprintf(out, "250 Okay\r\n");
fflush(out);
}
break;
case DEBUG_CMD:
if (out) {
#ifndef NODEBUG
if (smtp_debug) {
strip_rfc822_comments(data);
if (data[0]) {
error = NULL;
temp = c_atol(data, &error);
if (error) {
fprintf(out, "500 bad number: %s\r\n", error);
fflush(out);
break;
}
} else {
temp = 1;
}
if (temp == 0) {
fprintf(out, "250 Debugging disabled\r\n");
} else {
DEBUG(DBG_QUEUE_LO,
"debugging output grabbed by SMTP\r\n");
fprintf(out, "250 Debugging level: %d\r\n", temp);
}
fflush(out);
debug = temp;
errfile = out;
break;
}
#endif /* NODEBUG */
fprintf(out, "500 I hear you knocking, but you can't come in\r\n");
fflush(out);
}
break;
case HELP_CMD:
if (out) {
for (i = 0; i < TABLESIZE(help_msg); i++) {
fprintf(out, "%s\r\n", help_msg[i]);
}
fflush(out);
}
break;
case EOF_CMD:
if (out) {
fprintf(out, "421 %s Lost input channel\r\n", primary_name);
fflush(out);
}
files[file_i++] = NULL;
errfile = save_errfile;
debug = save_debug;
return files;
default:
if (out) {
fprintf(out, "500 Command unrecognized\r\n");
fflush(out);
}
break;
}
}
/*
* we appear to have received a SIGTERM, so shutdown and tell the
* remote host.
*/
fprintf(out, "421 %s Service not available, closing channel\r\n",
primary_name);
fflush(out);
files[file_i] = NULL;
errfile = save_errfile;
debug = save_debug;
return files;
}
static void
reset_state()
{
struct addr *cur;
struct addr *next;
for (cur = recipients; cur; cur = next) {
next = cur->succ;
xfree(cur->in_addr);
if (cur->work_addr) {
/* work_addr is defined only for interactive smtp */
xfree(cur->work_addr);
}
xfree((char *)cur);
}
recipients = NULL;
if (sender) {
xfree(sender);
sender = NULL;
}
}
static enum e_smtp_commands
read_smtp_command(f)
register FILE *f; /* SMTP command stream */
{
static struct str input; /* buffer storing recent command */
static int inited = FALSE; /* TRUE if input initialized */
register int c; /* input char */
static struct smtp_cmd_list {
char *name;
int len;
enum e_smtp_commands cmd;
} smtp_cmd_list[] = {
"HELO", sizeof("HELO")-1, HELO_CMD,
"MAIL FROM:", sizeof("MAIL FROM:")-1, MAIL_CMD,
"RCPT TO:", sizeof("RCPT TO:")-1, RCPT_CMD,
"DATA", sizeof("DATA")-1, DATA_CMD,
"VRFY", sizeof("VRFY")-1, VRFY_CMD,
"EXPN", sizeof("EXPN")-1, EXPN_CMD,
"QUIT", sizeof("QUIT")-1, QUIT_CMD,
"RSET", sizeof("RSET")-1, RSET_CMD,
"NOOP", sizeof("NOOP")-1, NOOP_CMD,
"DEBUG", sizeof("DEBUG")-1, DEBUG_CMD,
"HELP", sizeof("HELP")-1, HELP_CMD,
};
struct smtp_cmd_list *cp;
if (! inited) {
STR_INIT(&input);
inited = TRUE;
} else {
input.i = 0;
}
while ((c = getc(f)) != '\n' && c != EOF) {
STR_NEXT(&input, c);
}
if (input.p[input.i - 1] == '\r') {
input.p[input.i - 1] = '\0';
} else {
STR_NEXT(&input, '\0');
}
/* return end of file pseudo command if end of file encountered */
if (c == EOF) {
return EOF_CMD;
}
for (cp = smtp_cmd_list; cp < ENDTABLE(smtp_cmd_list); cp++) {
if (strncmpic(cp->name, input.p, cp->len) == 0) {
for (data = input.p + cp->len; isspace(*data); data++) ;
return cp->cmd;
}
}
return OTHER_CMD;
}
#ifndef NO_VERIFY
/*
* expand_addr - expand an address
*
* display the list of items that an address expands to.
*/
static void
expand_addr(in_addr, out)
char *in_addr; /* input address string */
FILE *out; /* write expansion here */
{
struct addr *addr = alloc_addr(); /* get an addr structure */
struct addr *okay = NULL; /* list of deliverable addrs */
struct addr *defer = NULL; /* list of currently unknown addrs */
struct addr *fail = NULL; /* list of undeliverable addrs */
char *error; /* hold error message */
addr->in_addr = in_addr; /* setup the input addr structure */
/* build the mungeable addr string */
addr->work_addr = preparse_address(in_addr, &error);
if (addr->work_addr == NULL) {
fprintf(out, "501 %s ... %s\r\n", in_addr, error);
fflush(out);
return;
}
/* cache directors and routers on the assumption we will need them again */
if (! queue_only) {
if (! cached_directors) {
cache_directors();
}
if (! cached_routers) {
cache_routers();
}
}
hit_table = new_hash_table(hit_table_len,
(struct block *)NULL,
HASH_DEFAULT);
resolve_addr_list(addr, &okay, &defer, &fail, TRUE);
if (okay) {
register struct addr *cur; /* current addr to display */
/* display the complete list of resolved addresses */
for (cur = okay; cur->succ; cur = cur->succ) {
fprintf(out, "250-%s\r\n", cur->in_addr);
fflush(out);
}
/* the last one should not begin with 250- */
fprintf(out, "250 %s\r\n", cur->in_addr);
} else {
/* just say we couldn't find it */
fprintf(out, "550 %s ... not matched\r\n", in_addr);
}
}
/*
* verify_addr - verify an address
*
* redisplay the input address if it is a valid address.
*/
static void
verify_addr(in_addr, out)
char *in_addr; /* input address string */
FILE *out; /* write expansion here */
{
struct addr *addr = alloc_addr(); /* get an addr structure */
struct addr *okay = NULL; /* verified address */
struct addr *defer = NULL; /* temporarily unverifiable addr */
struct addr *fail = NULL; /* unverified addr */
char *error; /* hold error message */
addr->in_addr = in_addr; /* setup the input addr structure */
/* build the mungeable addr string */
addr->work_addr = preparse_address(in_addr, &error);
if (addr->work_addr == NULL) {
fprintf(out, "501 %s ... %s\r\n", in_addr, error);
fflush(out);
return;
}
/* cache directors and routers on the assumption we will need them again */
if (! queue_only) {
if (! cached_directors) {
cache_directors();
}
if (! cached_routers) {
cache_routers();
}
}
verify_addr_list(addr, &okay, &defer, &fail);
if (okay) {
fprintf(out, "250 %s\r\n", in_addr);
} else if (defer) {
fprintf(out, "550 %s ... cannot verify: %s\r\n", in_addr,
defer->error->message);
} else if (fail) {
fprintf(out, "550 %s ... not matched: %s\r\n", in_addr,
fail->error->message);
} else {
/* hmmm, it should have been in one of the lists */
fprintf(out, "550 %s ... not matched\r\n", in_addr);
}
}
#endif /* NO_VERIFY */
/*
* smtp_input_signals - setup signals for reading in message with smtp
*
* Basically, unlink the message except in the case of SIGTERM, which
* will cause sig_term and queue_only to be set.
*/
static void
smtp_input_signals()
{
if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
(void) signal(SIGHUP, smtp_sig_unlink);
}
if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
(void) signal(SIGINT, smtp_sig_unlink);
}
(void) signal(SIGTERM, set_term_signal);
}
/*
* smtp_processing_signals - setup signals for getting smtp commands
*
* basically, everything interesting should cause a call to
* set_term_signal.
*/
static void
smtp_processing_signals()
{
if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
(void) signal(SIGHUP, set_term_signal);
}
if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
(void) signal(SIGINT, set_term_signal);
}
(void) signal(SIGTERM, set_term_signal);
}
/*
* set_term_signal - set term_signal and queue_only
*
* This is used by signals to abort SMTP command processing and to
* prevent attempting delivery.
*
* NOTE: This doesn't work correctly for systems that lack restartable
* system calls, as read will return EINTR for such systems,
* rather than continuing. This situation could be improved,
* though it doesn't really seem worth the rather large amount
* of bother required.
*/
static void
set_term_signal(sig)
int sig;
{
(void) signal(sig, set_term_signal);
term_signal = TRUE;
queue_only = TRUE;
}
/*
* smtp_receive_timeout_sig - timeout SMTP
*/
static void
smtp_receive_timeout_sig(sig)
int sig;
{
fprintf(out_file, "421 %s SMTP command timeout, closing channel\r\n",
primary_name);
write_log(LOG_SYS, "SMTP connection timeout%s%s.",
sender_host? "while talking with": "",
sender_host? sender_host: "");
if (smtp_remove_on_timeout) {
unlink_spool();
}
exit(EX_TEMPFAIL);
}
/*
* smtp_sig_unlink - unlink spool file and fast exit.
*
* This is useful for handling signals to abort reading a message in
* with SMTP.
*/
static void
smtp_sig_unlink(sig)
int sig;
{
(void) signal(sig, SIG_IGN);
if (out_file) {
fprintf(out_file, "421 %s Service not available, closing channel\r\n",
primary_name);
}
unlink_spool();
exit(EX_OSFILE);
}