This is smtprecv.c in view mode; [Download] [Up]
/* @(#)src/smtprecv.c 1.2 24 Oct 1990 05:25:16 */ /* * Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll * * 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" #ifndef DEPEND # include "extern.h" # include "debug.h" # include "exitcodes.h" #endif /* library functions */ extern long time(); /* 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_sig_unlink(); /* variables 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 */ }; static char *data; /* interesting data within input */ static int term_signal; static FILE *out_file; static char help_msg[] = "\ 250-The following SMTP commands are recognized:\r\n\ 250-\r\n\ 250- HELO hostname - startup and give your hostname\r\n\ 250- MAIL FROM:<sender-address> - start transaction from sender\r\n\ 250- RCPT TO:<recipient-address> - name recipient for message\r\n\ 250- VRFY <address> - verify deliverability of address\r\n\ 250- EXPN <address> - expand mailing list address\r\n\ 250- DATA - start text of mail message\r\n\ 250- RSET - reset state, drop transaction\r\n\ 250- NOOP - do nothing\r\n\ 250- DEBUG [level] - set debugging level, default 1\r\n\ 250- HELP - produce this help message\r\n\ 250- QUIT - close SMTP connection\r\n\ 250-\r\n\ 250-The normal sequence of events in sending a message is to state the\r\n\ 250-sender address with a MAIL FROM command, give the recipients with\r\n\ 250-as many RCPT TO commands as are required (one address per command)\r\n\ 250-and then to specify the mail message text after the DATA command.\r\n\ 250 Multiple messages may be specified. End the last one with a QUIT.\r\n\ "; /* * 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; /* 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(); /* allocate an initial chunk of spool filename slots */ if (files == NULL) { files = (char **)xmalloc(file_cnt * 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) { (void) fprintf(out, "220%c", index(s, '\n') == NULL? ' ': '-'); while (*s) { putc(*s, out); if (*s++ == '\n') break; } } putc('\r', out); putc('\n', out); (void) fflush(out); } while (! term_signal || out == NULL) { switch (read_smtp_command(in)) { case HELO_CMD: if (out) { (void)fprintf(out, "250 %s Hello %s\r\n", primary_name, data); (void)fflush(out); } reset_state(); break; case MAIL_CMD: if (sender) { (void) fprintf(out, "503 Sender already specified\r\n"); (void) fflush(out); break; } sender = preparse_address(data, &error); if (out) { if (sender) { (void) fprintf(out, "250 <%s> ... Sender Okay\r\n", sender); } else { (void) fprintf(out, "501 <%s> ... %s\r\n", data, error); } (void) fflush(out); } if (sender && sender[0] == '\0') { /* special error sender form <> given */ sender = COPY_STRING("<>"); } break; case RCPT_CMD: cur = alloc_addr(); if (out) { if (cur->work_addr = preparse_address(data, &error)) { (void) fprintf(out, "250 <%s> ... Recipient Okay\r\n", cur->work_addr); (void) fflush(out); } else { (void) fprintf(out, "501 <%s> ... %s\r\n", data, error); (void) 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) { (void) fprintf(out, "503 Need MAIL command\r\n"); (void) 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) { (void) fprintf(out, "503 Need RCPT (recpient)\r\n"); (void) fflush(out); } else { /* sink the message for the sake of further batched cmds */ if (spool_fn) { close_spool(); } swallow_smtp(in); } break; } if (out) { (void) fprintf(out, "354 Enter mail, end with \".\" on a line by itself\r\n"); (void) fflush(out); } /* * 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(); } if (queue_message(in, SMTP_DOTS, recipients, &error) == FAIL) { exitvalue = EX_IOERR; log_spool_errors(); if (out) { (void) fprintf(out, "451 Failed to queue message: %s: %s\r\n", error, strerrno()); (void) fflush(out); break; } else if (errfile) { (void) 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) { (void) fprintf(out, "451 error in spooled message\r\n"); (void) fflush(out); } break; } check_grade(); log_incoming(); log_spool_errors(); if (out) { (void) fprintf(out, "250 Mail accepted\r\n"); (void) 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 * sizeof(*files)); } files[file_i++] = xprintf("%s/input/%s", spool_dir, spool_fn); reset_state(); break; case VRFY_CMD: if (out) { #ifdef NO_VERIFY (void) fprintf(out, "502 Command not implemented\r\n"); #else verify_addr(data, out); (void) fflush(out); #endif } break; case EXPN_CMD: if (out) { #ifdef NO_VERIFY (void) fprintf(out, "502 Command not implemented\r\n"); #else expand_addr(data, out); (void) fflush(out); #endif } break; case QUIT_CMD: if (out) { (void) fprintf(out, "221 %s closing connection\r\n", primary_name); (void) fflush(out); } reset_state(); files[file_i++] = NULL; errfile = save_errfile; debug = save_debug; return files; case RSET_CMD: reset_state(); if (out) { (void) fprintf(out, "250 Reset state\r\n"); (void) fflush(out); } break; case NOOP_CMD: if (out) { (void) fprintf(out, "250 Okay\r\n"); (void) fflush(out); } break; case DEBUG_CMD: if (out) { #ifndef NODEBUG if (smtp_debug) { if (data[0]) { error = NULL; temp = c_atol(data, &error); if (error) { (void) fprintf(out, "500 bad number: %s\r\n", error); (void) fflush(out); break; } } else { temp = 1; } if (temp == 0) { (void) fprintf(out, "250 Debugging disabled\r\n"); } else { DEBUG(DBG_QUEUE_LO, "debugging output grabbed by SMTP\r\n"); (void) fprintf(out, "250 Debugging level: %d\r\n", temp); } (void) fflush(out); debug = temp; errfile = out; break; } #endif /* NODEBUG */ (void) fprintf(out, "500 I hear you knocking, but you can't come in\r\n"); (void) fflush(out); } break; case HELP_CMD: if (out) { (void) fprintf(out, "%s", help_msg); (void) fflush(out); } break; case EOF_CMD: if (out) { (void) fprintf(out, "421 %s Lost input channel\r\n", primary_name); (void) fflush(out); } files[file_i++] = NULL; errfile = save_errfile; debug = save_debug; return files; default: if (out) { (void) fprintf(out, "500 Command unrecognized\r\n"); (void) fflush(out); } break; } } /* * we appear to have received a SIGTERM, so shutdown and tell the * remote host. */ (void) fprintf(out, "421 %s Service not available, closing channel\r\n", primary_name); (void) fflush(out); files[file_i] = NULL; errfile = save_errfile; debug = save_debug; return files; } static void reset_state() { struct addr *cur; struct addr *tmp; cur = recipients; while (cur) { xfree(cur->in_addr); if (cur->work_addr) { /* work_addr is defined only for interactive smtp */ xfree(cur->work_addr); } tmp = cur->succ; xfree((char *)cur); cur = tmp; } 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) { (void) fprintf(out, "501 %s ... %s\r\n", in_addr, error); (void) 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(); } } resolve_addr_list(addr, &okay, &defer, &fail, FALSE); 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) { (void) fprintf(out, "250-%s\r\n", cur->in_addr); (void) fflush(out); } /* the last one should not begin with 250- */ (void) fprintf(out, "250 %s\r\n", cur->in_addr); } else { /* just say we couldn't find it */ (void) 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) { (void) fprintf(out, "501 %s ... %s\r\n", in_addr, error); (void) 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) { (void) fprintf(out, "250 %s\r\n", in_addr); } else if (defer) { (void) fprintf(out, "550 %s ... cannot verify: %s\r\n", in_addr, defer->error->message); } else if (fail) { (void) 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 */ (void) 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(SIGALRM, SIG_IGN); (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(SIGALRM, SIG_IGN); (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_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) { (void) fprintf(out_file, "421 %s Service not available, closing channel\r\n", primary_name); } unlink_spool(); exit(EX_OSFILE); }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.