NetBSD/gnu/dist/postfix/smtpd/smtpd.c

1374 lines
42 KiB
C

/*++
/* NAME
/* smtpd 8
/* SUMMARY
/* Postfix SMTP server
/* SYNOPSIS
/* \fBsmtpd\fR [generic Postfix daemon options]
/* DESCRIPTION
/* The SMTP server accepts network connection requests
/* and performs zero or more SMTP transactions per connection.
/* Each received message is piped through the \fBcleanup\fR(8)
/* daemon, and is placed into the \fBincoming\fR queue as one
/* single queue file. For this mode of operation, the program
/* expects to be run from the \fBmaster\fR(8) process manager.
/*
/* Alternatively, the SMTP server takes an established
/* connection on standard input and deposits messages directly
/* into the \fBmaildrop\fR queue. In this so-called stand-alone
/* mode, the SMTP server can accept mail even while the mail
/* system is not running.
/*
/* The SMTP server implements a variety of policies for connection
/* requests, and for parameters given to \fBHELO, MAIL FROM, VRFY\fR
/* and \fBRCPT TO\fR commands. They are detailed below and in the
/* \fBmain.cf\fR configuration file.
/* SECURITY
/* .ad
/* .fi
/* The SMTP server is moderately security-sensitive. It talks to SMTP
/* clients and to DNS servers on the network. The SMTP server can be
/* run chrooted at fixed low privilege.
/* STANDARDS
/* RFC 821 (SMTP protocol)
/* RFC 1123 (Host requirements)
/* RFC 1651 (SMTP service extensions)
/* RFC 1652 (8bit-MIME transport)
/* RFC 1854 (SMTP Pipelining)
/* RFC 1870 (Message Size Declaration)
/* RFC 1985 (ETRN command) (partial)
/* DIAGNOSTICS
/* Problems and transactions are logged to \fBsyslogd\fR(8).
/*
/* Depending on the setting of the \fBnotify_classes\fR parameter,
/* the postmaster is notified of bounces, protocol problems,
/* policy violations, and of other trouble.
/* BUGS
/* RFC 1985 is implemented by forcing delivery of all deferred mail.
/* CONFIGURATION PARAMETERS
/* .ad
/* .fi
/* The following \fBmain.cf\fR parameters are especially relevant to
/* this program. See the Postfix \fBmain.cf\fR file for syntax details
/* and for default values. Use the \fBpostfix reload\fR command after
/* a configuration change.
/* .SH "Compatibility controls"
/* .ad
/* .fi
/* .IP \fBstrict_rfc821_envelopes\fR
/* Disallow non-RFC 821 style addresses in envelopes. For example,
/* allow RFC822-style address forms with comments, like Sendmail does.
/* .SH Miscellaneous
/* .ad
/* .fi
/* .IP \fBalways_bcc\fR
/* Address to send a copy of each message that enters the system.
/* .IP \fBcommand_directory\fR
/* Location of Postfix support commands (default:
/* \fB$program_directory\fR).
/* .IP \fBdebug_peer_level\fR
/* Increment in verbose logging level when a remote host matches a
/* pattern in the \fBdebug_peer_list\fR parameter.
/* .IP \fBdebug_peer_list\fR
/* List of domain or network patterns. When a remote host matches
/* a pattern, increase the verbose logging level by the amount
/* specified in the \fBdebug_peer_level\fR parameter.
/* .IP \fBerror_notice_recipient\fR
/* Recipient of protocol/policy/resource/software error notices.
/* .IP \fBhopcount_limit\fR
/* Limit the number of \fBReceived:\fR message headers.
/* .IP \fBnotify_classes\fR
/* List of error classes. Of special interest are:
/* .IP \fBlocal_recipient_maps\fR
/* List of maps with user names that are local to \fB$myorigin\fR
/* or \fB$inet_interfaces\fR. If this parameter is defined,
/* then the SMTP server rejects mail for unknown local users.
/* .RS
/* .IP \fBpolicy\fR
/* When a client violates any policy, mail a transcript of the
/* entire SMTP session to the postmaster.
/* .IP \fBprotocol\fR
/* When a client violates the SMTP protocol or issues an unimplemented
/* command, mail a transcript of the entire SMTP session to the
/* postmaster.
/* .RE
/* .IP \fBsmtpd_banner\fR
/* Text that follows the \fB220\fR status code in the SMTP greeting banner.
/* .IP \fBsmtpd_recipient_limit\fR
/* Restrict the number of recipients that the SMTP server accepts
/* per message delivery.
/* .IP \fBsmtpd_timeout\fR
/* Limit the time to send a server response and to receive a client
/* request.
/* .SH "Resource controls"
/* .ad
/* .fi
/* .IP \fBline_length_limit\fR
/* Limit the amount of memory in bytes used for the handling of
/* partial input lines.
/* .IP \fBmessage_size_limit\fR
/* Limit the total size in bytes of a message, including on-disk
/* storage for envelope information.
/* .IP \fBqueue_minfree\fR
/* Minimal amount of free space in bytes in the queue file system
/* for the SMTP server to accept any mail at all.
/* .SH Tarpitting
/* .ad
/* .fi
/* .IP \fBsmtpd_error_sleep_time\fR
/* Time to wait in seconds before sending a 4xx or 5xx server error
/* response.
/* .IP \fBsmtpd_soft_error_limit\fR
/* When an SMTP client has made this number of errors, wait
/* \fIerror_count\fR seconds before responding to any client request.
/* .IP \fBsmtpd_hard_error_limit\fR
/* Disconnect after a client has made this number of errors.
/* .IP \fBsmtpd_junk_command_limit\fR
/* Limit the number of times a client can issue a junk command
/* such as NOOP, VRFY, ETRN or RSET in one SMTP session before
/* it is penalized with tarpit delays.
/* .SH "UCE control restrictions"
/* .ad
/* .fi
/* .IP \fBsmtpd_client_restrictions\fR
/* Restrict what clients may connect to this mail system.
/* .IP \fBsmtpd_helo_required\fR
/* Require that clients introduce themselves at the beginning
/* of an SMTP session.
/* .IP \fBsmtpd_helo_restrictions\fR
/* Restrict what client hostnames are allowed in \fBHELO\fR and
/* \fBEHLO\fR commands.
/* .IP \fBsmtpd_sender_restrictions\fR
/* Restrict what sender addresses are allowed in \fBMAIL FROM\fR commands.
/* .IP \fBsmtpd_recipient_restrictions\fR
/* Restrict what recipient addresses are allowed in \fBRCPT TO\fR commands.
/* .IP \fBsmtpd_etrn_restrictions\fR
/* Restrict what domain names can be used in \fBETRN\fR commands,
/* and what clients may issue \fBETRN\fR commands.
/* .IP \fBallow_untrusted_routing\fR
/* Allow untrusted clients to specify addresses with sender-specified
/* routing. Enabling this opens up nasty relay loopholes involving
/* trusted backup MX hosts.
/* .IP \fBrestriction_classes\fR
/* Declares the name of zero or more parameters that contain a
/* list of UCE restrictions. The names of these parameters can
/* then be used instead of the restriction lists that they represent.
/* .IP \fBmaps_rbl_domains\fR
/* List of DNS domains that publish the addresses of blacklisted
/* hosts.
/* .IP \fBrelay_domains\fR
/* Restrict what domains or networks this mail system will relay
/* mail from or to.
/* .SH "UCE control responses"
/* .ad
/* .fi
/* .IP \fBaccess_map_reject_code\fR
/* Server response when a client violates an access database restriction.
/* .IP \fBinvalid_hostname_reject_code\fR
/* Server response when a client violates the \fBreject_invalid_hostname\fR
/* restriction.
/* .IP \fBmaps_rbl_reject_code\fR
/* Server response when a client violates the \fBmaps_rbl_domains\fR
/* restriction.
/* .IP \fBreject_code\fR
/* Response code when the client matches a \fBreject\fR restriction.
/* .IP \fBrelay_domains_reject_code\fR
/* Server response when a client attempts to violate the mail relay
/* policy.
/* .IP \fBunknown_address_reject_code\fR
/* Server response when a client violates the \fBreject_unknown_address\fR
/* restriction.
/* .IP \fBunknown_client_reject_code\fR
/* Server response when a client without address to name mapping
/* violates the \fBreject_unknown_clients\fR restriction.
/* .IP \fBunknown_hostname_reject_code\fR
/* Server response when a client violates the \fBreject_unknown_hostname\fR
/* restriction.
/* SEE ALSO
/* cleanup(8) message canonicalization
/* master(8) process manager
/* syslogd(8) system logging
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*--*/
/* System library. */
#include <sys_defs.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h> /* remove() */
#include <setjmp.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
/* Utility library. */
#include <msg.h>
#include <mymalloc.h>
#include <vstring.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <stringops.h>
#include <events.h>
#include <smtp_stream.h>
#include <valid_hostname.h>
#include <dict.h>
/* Global library. */
#include <mail_params.h>
#include <record.h>
#include <rec_type.h>
#include <mail_proto.h>
#include <cleanup_user.h>
#include <mail_date.h>
#include <mail_conf.h>
#include <off_cvt.h>
#include <debug_peer.h>
#include <mail_error.h>
#include <mail_flush.h>
#include <mail_stream.h>
#include <mail_queue.h>
#include <tok822.h>
/* Single-threaded server skeleton. */
#include <mail_server.h>
/* Application-specific */
#include "smtpd_token.h"
#include "smtpd.h"
#include "smtpd_check.h"
#include "smtpd_chat.h"
/*
* Tunable parameters. Make sure that there is some bound on the length of
* an SMTP command, so that the mail system stays in control even when a
* malicious client sends commands of unreasonable length (qmail-dos-1).
* Make sure there is some bound on the number of recipients, so that the
* mail system stays in control even when a malicious client sends an
* unreasonable number of recipients (qmail-dos-2).
*/
int var_smtpd_rcpt_limit;
int var_smtpd_tmout;
char *var_relay_domains;
int var_smtpd_soft_erlim;
int var_smtpd_hard_erlim;
int var_queue_minfree; /* XXX use off_t */
char *var_smtpd_banner;
char *var_debug_peer_list;
int var_debug_peer_level;
char *var_notify_classes;
char *var_client_checks;
char *var_helo_checks;
char *var_mail_checks;
char *var_rcpt_checks;
char *var_etrn_checks;
int var_unk_client_code;
int var_bad_name_code;
int var_unk_name_code;
int var_unk_addr_code;
int var_relay_code;
int var_maps_rbl_code;
int var_access_map_code;
char *var_maps_rbl_domains;
int var_helo_required;
int var_reject_code;
int var_smtpd_err_sleep;
int var_non_fqdn_code;
char *var_always_bcc;
char *var_error_rcpt;
int var_smtpd_delay_reject;
char *var_rest_classes;
int var_strict_rfc821_env;
bool var_disable_vrfy_cmd;
char *var_canonical_maps;
char *var_rcpt_canon_maps;
char *var_virtual_maps;
char *var_relocated_maps;
char *var_alias_maps;
char *var_local_rcpt_maps;
bool var_allow_untrust_route;
int var_smtpd_junk_cmd_limit;
/*
* Global state, for stand-alone mode queue file cleanup. When this is
* non-null at cleanup time, the named file is removed.
*/
char *smtpd_path;
/*
* Silly little macros.
*/
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
/* collapse_args - put arguments together again */
static void collapse_args(int argc, SMTPD_TOKEN *argv)
{
int i;
for (i = 1; i < argc; i++) {
vstring_strcat(argv[0].vstrval, " ");
vstring_strcat(argv[0].vstrval, argv[i].strval);
}
argv[0].strval = STR(argv[0].vstrval);
}
/* helo_cmd - process HELO command */
static int helo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
{
char *err;
if (argc < 2) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Syntax: HELO hostname");
return (-1);
}
if (state->helo_name != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "503 Duplicate HELO/EHLO");
return (-1);
}
if (argc > 2)
collapse_args(argc - 1, argv + 1);
if (SMTPD_STAND_ALONE(state) == 0
&& var_smtpd_delay_reject == 0
&& (err = smtpd_check_helo(state, argv[1].strval)) != 0) {
smtpd_chat_reply(state, "%s", err);
return (-1);
}
state->helo_name = mystrdup(printable(argv[1].strval, '?'));
state->protocol = "SMTP";
smtpd_chat_reply(state, "250 %s", var_myhostname);
return (0);
}
/* ehlo_cmd - process EHLO command */
static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
{
char *err;
if (argc < 2) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Syntax: EHLO hostname");
return (-1);
}
if (state->helo_name != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "503 Error: duplicate HELO/EHLO");
return (-1);
}
if (argc > 2)
collapse_args(argc - 1, argv + 1);
if (SMTPD_STAND_ALONE(state) == 0
&& var_smtpd_delay_reject == 0
&& (err = smtpd_check_helo(state, argv[1].strval)) != 0) {
smtpd_chat_reply(state, "%s", err);
return (-1);
}
state->helo_name = mystrdup(printable(argv[1].strval, '?'));
state->protocol = "ESMTP";
smtpd_chat_reply(state, "250-%s", var_myhostname);
smtpd_chat_reply(state, "250-PIPELINING");
if (var_message_limit)
smtpd_chat_reply(state, "250-SIZE %lu",
(unsigned long) var_message_limit); /* XXX */
else
smtpd_chat_reply(state, "250-SIZE");
smtpd_chat_reply(state, "250-ETRN");
smtpd_chat_reply(state, "250 8BITMIME");
return (0);
}
/* helo_reset - reset HELO/EHLO command stuff */
static void helo_reset(SMTPD_STATE *state)
{
if (state->helo_name)
myfree(state->helo_name);
state->helo_name = 0;
}
/* mail_open_stream - open mail destination */
static void mail_open_stream(SMTPD_STATE *state)
{
char *postdrop_command;
/*
* If running from the master or from inetd, connect to the cleanup
* service.
*/
if (SMTPD_STAND_ALONE(state) == 0) {
state->dest = mail_stream_service(MAIL_CLASS_PRIVATE,
MAIL_SERVICE_CLEANUP);
if (state->dest == 0
|| mail_print(state->dest->stream, "%d", CLEANUP_FLAG_FILTER) != 0)
msg_fatal("unable to connect to the %s %s service",
MAIL_CLASS_PRIVATE, MAIL_SERVICE_CLEANUP);
}
/*
* Otherwise, if the maildrop is writable, create a maildrop file.
* Arrange for pickup service notification. Make a copy of the pathname
* so that the file can be deleted in case of a fatal run-time error.
*/
else if (access(MAIL_QUEUE_MAILDROP, W_OK) == 0) {
state->dest = mail_stream_file(MAIL_QUEUE_MAILDROP,
MAIL_CLASS_PUBLIC, MAIL_SERVICE_PICKUP);
smtpd_path = mystrdup(VSTREAM_PATH(state->dest->stream));
}
/*
* Otherwise, pipe the message through the privileged postdrop helper.
* XXX Make postdrop a manifest constant.
*/
else {
postdrop_command = concatenate(var_command_dir, "/postdrop",
msg_verbose ? " -v" : (char *) 0, (char *) 0);
state->dest = mail_stream_command(postdrop_command);
if (state->dest == 0)
msg_fatal("unable to execute %s", postdrop_command);
myfree(postdrop_command);
}
state->cleanup = state->dest->stream;
state->queue_id = mystrdup(state->dest->id);
}
/* extract_addr - extract address from rubble */
static char *extract_addr(SMTPD_STATE *state, SMTPD_TOKEN *arg,
int allow_empty_addr, int strict_rfc821)
{
char *myname = "extract_addr";
TOK822 *tree;
TOK822 *tp;
TOK822 *addr = 0;
int naddr;
int non_addr;
char *err = 0;
/*
* Special case.
*/
#define PERMIT_EMPTY_ADDR 1
#define REJECT_EMPTY_ADDR 0
/*
* Some mailers send RFC822-style address forms (with comments and such)
* in SMTP envelopes. We cannot blame users for this: the blame is with
* programmers violating the RFC, and with sendmail for being permissive.
*
* XXX The SMTP command tokenizer must leave the address in externalized
* (quoted) form, so that the address parser can correctly extract the
* address from surrounding junk.
*
* XXX We have only one address parser, written according to the rules of
* RFC 822. That standard differs subtly from RFC 821.
*/
if (msg_verbose)
msg_info("%s: input: %s", myname, STR(arg->vstrval));
tree = tok822_parse(STR(arg->vstrval));
/*
* Find trouble.
*/
for (naddr = non_addr = 0, tp = tree; tp != 0; tp = tp->next) {
if (tp->type == TOK822_ADDR) {
addr = tp;
naddr += 1; /* count address forms */
} else if (tp->type == '<' || tp->type == '>') {
/* void */ ; /* ignore brackets */
} else {
non_addr += 1; /* count non-address forms */
}
}
/*
* Report trouble. Log a warning only if we are going to sleep+reject so
* that attackers can't flood our logfiles.
*/
if ((naddr < 1 && !allow_empty_addr)
|| naddr > 1
|| (strict_rfc821 && (non_addr || *STR(arg->vstrval) != '<'))) {
msg_warn("Illegal address syntax from %s in %s command: %s",
state->namaddr, state->where, STR(arg->vstrval));
err = "501 Bad address syntax";
}
/*
* Overwrite the input with the extracted address. This seems bad design,
* but we really are not going to use the original data anymore. What we
* start with is quoted (external) form, and what we need is unquoted
* (internal form).
*/
if (addr)
tok822_internalize(arg->vstrval, addr->head, TOK822_STR_DEFL);
else
vstring_strcpy(arg->vstrval, "");
arg->strval = STR(arg->vstrval);
/*
* Cleanup.
*/
tok822_free_tree(tree);
if (msg_verbose)
msg_info("%s: result: %s", myname, STR(arg->vstrval));
return (err);
}
/* mail_cmd - process MAIL command */
static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
{
char *err;
int narg;
off_t size = 0;
char *arg;
/*
* Sanity checks. XXX Ignore bad SIZE= values until we can reliably and
* portably detect overflows while converting from string to off_t.
*/
if (var_helo_required && state->helo_name == 0) {
state->error_mask |= MAIL_ERROR_POLICY;
smtpd_chat_reply(state, "503 Error: send HELO/EHLO first");
return (-1);
}
if (state->cleanup != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "503 Error: nested MAIL command");
return (-1);
}
if (argc < 3
|| strcasecmp(argv[1].strval, "from:") != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Syntax: MAIL FROM: <address>");
return (-1);
}
if (argv[2].tokval == SMTPD_TOK_ERROR) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Bad address syntax");
return (-1);
}
if ((err = extract_addr(state, argv + 2, PERMIT_EMPTY_ADDR, var_strict_rfc821_env)) != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "%s", err);
return (-1);
}
for (narg = 3; narg < argc; narg++) {
arg = argv[narg].strval;
if (strcasecmp(arg, "BODY=8BITMIME") == 0
|| strcasecmp(arg, "BODY=7BIT") == 0) {
/* void */ ;
} else if (strncasecmp(arg, "SIZE=", 5) == 0) {
if ((size = off_cvt_string(arg + 5)) < 0)
size = 0;
} else {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "555 Unsupported option: %s", arg);
return (-1);
}
}
state->time = time((time_t *) 0);
if (SMTPD_STAND_ALONE(state) == 0
&& var_smtpd_delay_reject == 0
&& (err = smtpd_check_mail(state, argv[2].strval)) != 0) {
smtpd_chat_reply(state, "%s", err);
return (-1);
}
if ((err = smtpd_check_size(state, size)) != 0) {
smtpd_chat_reply(state, "%s", err);
return (-1);
}
/*
* Open queue file or IPC stream.
*/
mail_open_stream(state);
msg_info("%s: client=%s[%s]", state->queue_id, state->name, state->addr);
/*
* Record the time of arrival and the sender envelope address.
*/
rec_fprintf(state->cleanup, REC_TYPE_TIME, "%ld",
(long) time((time_t *) 0));
rec_fputs(state->cleanup, REC_TYPE_FROM, argv[2].strval);
state->sender = mystrdup(argv[2].strval);
smtpd_chat_reply(state, "250 Ok");
return (0);
}
/* mail_reset - reset MAIL command stuff */
static void mail_reset(SMTPD_STATE *state)
{
/*
* Unceremoniously close the pipe to the cleanup service. The cleanup
* service will delete the queue file when it detects a premature
* end-of-file condition on input.
*/
if (state->cleanup != 0) {
mail_stream_cleanup(state->dest);
state->dest = 0;
state->cleanup = 0;
}
state->err = 0;
if (state->queue_id != 0) {
myfree(state->queue_id);
state->queue_id = 0;
}
if (smtpd_path) {
if (remove(smtpd_path))
msg_warn("remove %s: %m", smtpd_path);
else if (msg_verbose)
msg_info("remove %s", smtpd_path);
myfree(smtpd_path);
smtpd_path = 0;
}
if (state->sender) {
myfree(state->sender);
state->sender = 0;
}
}
/* rcpt_cmd - process RCPT TO command */
static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
{
char *err;
/*
* Sanity checks.
*/
if (state->cleanup == 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "503 Error: need MAIL command");
return (-1);
}
if (argc != 3
|| strcasecmp(argv[1].strval, "to:") != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Syntax: RCPT TO: <address>");
return (-1);
}
if (argv[2].tokval == SMTPD_TOK_ERROR) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Bad address syntax");
return (-1);
}
if ((err = extract_addr(state, argv + 2, REJECT_EMPTY_ADDR, var_strict_rfc821_env)) != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "%s", err);
return (-1);
}
if (var_smtpd_rcpt_limit && state->rcpt_count >= var_smtpd_rcpt_limit) {
state->error_mask |= MAIL_ERROR_POLICY;
smtpd_chat_reply(state, "452 Error: too many recipients");
return (-1);
}
if (SMTPD_STAND_ALONE(state) == 0) {
if ((err = smtpd_check_rcpt(state, argv[2].strval)) != 0) {
smtpd_chat_reply(state, "%s", err);
return (-1);
}
if ((err = smtpd_check_rcptmap(state, argv[2].strval)) != 0) {
smtpd_chat_reply(state, "%s", err);
return (-1);
}
}
/*
* Store the recipient. Remember the first one.
*/
state->rcpt_count++;
if (state->recipient == 0)
state->recipient = mystrdup(argv[2].strval);
rec_fputs(state->cleanup, REC_TYPE_RCPT, argv[2].strval);
smtpd_chat_reply(state, "250 Ok");
return (0);
}
/* rcpt_reset - reset RCPT stuff */
static void rcpt_reset(SMTPD_STATE *state)
{
if (state->recipient) {
myfree(state->recipient);
state->recipient = 0;
}
state->rcpt_count = 0;
}
/* data_cmd - process DATA command */
static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
{
char *start;
int len;
int curr_rec_type;
int prev_rec_type;
int first = 1;
/*
* Sanity checks. With ESMTP command pipelining the client can send DATA
* before all recipients are rejected, so don't report that as a protocol
* error.
*/
if (state->rcpt_count == 0) {
if (state->cleanup == 0)
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "503 Error: need RCPT command");
return (-1);
}
if (argc != 1) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Syntax: DATA");
return (-1);
}
/*
* Terminate the message envelope segment. Start the message content
* segment, and prepend our own Received: header. If there is only one
* recipient, list the recipient address.
*/
if (*var_always_bcc)
rec_fputs(state->cleanup, REC_TYPE_RCPT, var_always_bcc);
rec_fputs(state->cleanup, REC_TYPE_MESG, "");
rec_fprintf(state->cleanup, REC_TYPE_NORM,
"Received: from %s (%s [%s])",
state->helo_name ? state->helo_name : state->name,
state->name, state->addr);
if (state->rcpt_count == 1 && state->recipient) {
rec_fprintf(state->cleanup, REC_TYPE_NORM,
"\tby %s (%s) with %s id %s",
var_myhostname, var_mail_name,
state->protocol, state->queue_id);
rec_fprintf(state->cleanup, REC_TYPE_NORM,
"\tfor <%s>; %s", state->recipient, mail_date(state->time));
} else {
rec_fprintf(state->cleanup, REC_TYPE_NORM,
"\tby %s (%s) with %s",
var_myhostname, var_mail_name, state->protocol);
rec_fprintf(state->cleanup, REC_TYPE_NORM,
"\tid %s; %s", state->queue_id, mail_date(state->time));
}
smtpd_chat_reply(state, "354 End data with <CR><LF>.<CR><LF>");
/*
* Copy the message content. If the cleanup process has a problem, keep
* reading until the remote stops sending, then complain. Read typed
* records from the SMTP stream so we can handle data that spans buffers.
*
* XXX Force an empty record when the queue file content begins with
* whitespace, so that it won't be considered as being part of our own
* Received: header. What an ugly Kluge.
*/
if (vstream_fflush(state->cleanup))
state->err = CLEANUP_STAT_WRITE;
for (prev_rec_type = 0; /* void */ ; prev_rec_type = curr_rec_type) {
if (smtp_get(state->buffer, state->client, var_line_limit) == '\n')
curr_rec_type = REC_TYPE_NORM;
else
curr_rec_type = REC_TYPE_CONT;
start = vstring_str(state->buffer);
len = VSTRING_LEN(state->buffer);
if (first) {
first = 0;
if (len > 0 && ISSPACE(start[0]))
rec_put(state->cleanup, REC_TYPE_NORM, "", 0);
}
if (prev_rec_type != REC_TYPE_CONT
&& *start == '.' && (++start, --len) == 0)
break;
if (state->err == CLEANUP_STAT_OK
&& rec_put(state->cleanup, curr_rec_type, start, len) < 0)
state->err = CLEANUP_STAT_WRITE;
}
/*
* Send the end-of-segment markers.
*/
if (state->err == CLEANUP_STAT_OK)
if (rec_fputs(state->cleanup, REC_TYPE_XTRA, "") < 0
|| rec_fputs(state->cleanup, REC_TYPE_END, "") < 0
|| vstream_fflush(state->cleanup))
state->err = CLEANUP_STAT_WRITE;
/*
* Finish the queue file or finish the cleanup conversation.
*/
if (state->err == 0)
state->err |= mail_stream_finish(state->dest);
else
mail_stream_cleanup(state->dest);
state->dest = 0;
state->cleanup = 0;
/*
* Delete the queue file or disable delete on fatal error or interrupt.
*/
if (smtpd_path) {
if (state->err != 0) {
if (remove(smtpd_path))
msg_warn("remove %s: %m", smtpd_path);
else if (msg_verbose)
msg_info("remove %s", smtpd_path);
}
myfree(smtpd_path);
smtpd_path = 0;
}
/*
* Handle any errors. One message may suffer from multiple errors, so
* complain only about the most severe error. Forgive any previous client
* errors when a message was received successfully.
*/
if (state->err == CLEANUP_STAT_OK) {
state->error_count = 0;
state->error_mask = 0;
smtpd_chat_reply(state, "250 Ok: queued as %s", state->queue_id);
} else if ((state->err & CLEANUP_STAT_BAD) != 0) {
state->error_mask |= MAIL_ERROR_SOFTWARE;
smtpd_chat_reply(state, "451 Error: internal error %d", state->err);
} else if ((state->err & CLEANUP_STAT_SIZE) != 0) {
state->error_mask |= MAIL_ERROR_BOUNCE;
smtpd_chat_reply(state, "552 Error: message too large");
} else if ((state->err & CLEANUP_STAT_HOPS) != 0) {
state->error_mask |= MAIL_ERROR_BOUNCE;
smtpd_chat_reply(state, "554 Error: too many hops");
} else if ((state->err & CLEANUP_STAT_CONT) != 0) {
state->error_mask |= MAIL_ERROR_BOUNCE;
smtpd_chat_reply(state, "552 Error: content rejected");
} else if ((state->err & CLEANUP_STAT_WRITE) != 0) {
state->error_mask |= MAIL_ERROR_RESOURCE;
smtpd_chat_reply(state, "451 Error: queue file write error");
} else if ((state->err & CLEANUP_STAT_RCPT) != 0) {
state->error_mask |= MAIL_ERROR_SOFTWARE;
smtpd_chat_reply(state, "451 Error: internal error %d", state->err);
} else {
msg_panic("data_cmd: unknown status %d", state->err);
}
/*
* Disconnect after transmission must not be treated as "lost connection
* after DATA".
*/
state->where = SMTPD_AFTER_DOT;
/*
* Cleanup. The client may send another MAIL command.
*/
mail_reset(state);
rcpt_reset(state);
return (state->err);
}
/* rset_cmd - process RSET */
static int rset_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
{
/*
* Sanity checks.
*/
if (argc != 1) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Syntax: RSET");
return (-1);
}
/*
* Restore state to right after HELO/EHLO command.
*/
mail_reset(state);
rcpt_reset(state);
smtpd_chat_reply(state, "250 Ok");
return (0);
}
/* noop_cmd - process NOOP */
static int noop_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
{
/*
* Sanity checks.
*/
if (argc != 1) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Syntax: NOOP");
return (-1);
}
smtpd_chat_reply(state, "250 Ok");
return (0);
}
/* vrfy_cmd - process VRFY */
static int vrfy_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
{
char *err = 0;
/*
* The SMTP standard (RFC 821) disallows unquoted special characters in
* the VRFY argument. Common practice violates the standard, however.
* Postfix accomodates common practice where it violates the standard.
*
* XXX Impedance mismatch! The SMTP command tokenizer preserves quoting,
* whereas the recipient restrictions checks expect unquoted (internal)
* address forms. Therefore we must parse out the address, or we must
* stop doing recipient restriction checks and lose the opportunity to
* say "user unknown" at the SMTP port.
*/
#define SLOPPY 0
if (var_disable_vrfy_cmd) {
state->error_mask |= MAIL_ERROR_POLICY;
smtpd_chat_reply(state, "502 VRFY command is disabled");
return (-1);
}
if (argc < 2) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Syntax: VRFY address");
return (-1);
}
if (argc > 2)
collapse_args(argc - 1, argv + 1);
if ((err = extract_addr(state, argv + 1, REJECT_EMPTY_ADDR, SLOPPY)) != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "%s", err);
return (-1);
}
if (SMTPD_STAND_ALONE(state) == 0
&& (err = smtpd_check_rcptmap(state, argv[1].strval)) != 0) {
smtpd_chat_reply(state, "%s", err);
return (-1);
}
smtpd_chat_reply(state, "252 <%s>", argv[1].strval);
return (0);
}
/* etrn_cmd - process ETRN command */
static int etrn_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv)
{
char *err;
/*
* Sanity checks.
*/
if (var_helo_required && state->helo_name == 0) {
state->error_mask |= MAIL_ERROR_POLICY;
smtpd_chat_reply(state, "503 Error: send HELO/EHLO first");
return (-1);
}
if (state->cleanup != 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "503 Error: MAIL transaction in progress");
return (-1);
}
if (argc != 2) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "500 Syntax: ETRN domain");
return (-1);
}
if (!ISALNUM(argv[1].strval[0]))
argv[1].strval++;
if (!valid_hostname(argv[1].strval)) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "501 Error: invalid parameter syntax");
return (-1);
}
/*
* XXX The implementation borrows heavily from the code that implements
* UCE restrictions. These typically return 450 or 550 when a request is
* rejected. RFC 1985 requires that 459 be sent when the server refuses
* to perform the request.
*/
if (SMTPD_STAND_ALONE(state) == 0
&& (err = smtpd_check_etrn(state, argv[1].strval)) != 0) {
smtpd_chat_reply(state, "%s", err);
return (-1);
}
/*
* XXX The preliminary implementation causes a full deferred queue scan.
*/
if (mail_flush_site(argv[1].strval) < 0)
smtpd_chat_reply(state, "458 Unable to queue messages");
else
smtpd_chat_reply(state, "250 Queuing started");
return (0);
}
/* quit_cmd - process QUIT command */
static int quit_cmd(SMTPD_STATE *state, int unused_argc, SMTPD_TOKEN *unused_argv)
{
/*
* Don't bother checking the syntax.
*/
smtpd_chat_reply(state, "221 Bye");
return (0);
}
/*
* The table of all SMTP commands that we know.
*/
typedef struct SMTPD_CMD {
char *name;
int (*action) (SMTPD_STATE *, int, SMTPD_TOKEN *);
int flags;
} SMTPD_CMD;
#define SMTPD_CMD_FLAG_LIMIT (1<<0) /* limit usage */
static SMTPD_CMD smtpd_cmd_table[] = {
"HELO", helo_cmd, 0,
"EHLO", ehlo_cmd, 0,
"MAIL", mail_cmd, 0,
"RCPT", rcpt_cmd, 0,
"DATA", data_cmd, 0,
"RSET", rset_cmd, SMTPD_CMD_FLAG_LIMIT,
"NOOP", noop_cmd, SMTPD_CMD_FLAG_LIMIT,
"VRFY", vrfy_cmd, SMTPD_CMD_FLAG_LIMIT,
"ETRN", etrn_cmd, SMTPD_CMD_FLAG_LIMIT,
"QUIT", quit_cmd, 0,
0,
};
/* smtpd_proto - talk the SMTP protocol */
static void smtpd_proto(SMTPD_STATE *state)
{
int argc;
SMTPD_TOKEN *argv;
SMTPD_CMD *cmdp;
/*
* Print a greeting banner and run the state machine. Read SMTP commands
* one line at a time. According to the standard, a sender or recipient
* address could contain an escaped newline. I think this is perverse,
* and anyone depending on this is really asking for trouble.
*
* In case of mail protocol trouble, the program jumps back to this place,
* so that it can perform the necessary cleanup before talking to the
* next client. The setjmp/longjmp primitives are like a sharp tool: use
* with care. I would certainly recommend against the use of
* setjmp/longjmp in programs that change privilege levels.
*
* In case of file system trouble the program terminates after logging the
* error and after informing the client. In all other cases (out of
* memory, panic) the error is logged, and the msg_cleanup() exit handler
* cleans up, but no attempt is made to inform the client of the nature
* of the problem.
*/
smtp_timeout_setup(state->client, var_smtpd_tmout);
switch (setjmp(smtp_timeout_buf)) {
default:
msg_panic("smtpd_proto: unknown error reading from %s[%s]",
state->name, state->addr);
break;
case SMTP_ERR_TIME:
state->reason = "timeout";
smtpd_chat_reply(state, "421 Error: timeout exceeded");
break;
case SMTP_ERR_EOF:
state->reason = "lost connection";
break;
case 0:
for (;;) {
if (state->error_count > var_smtpd_hard_erlim) {
state->reason = "too many errors";
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "421 Error: too many errors");
break;
}
smtpd_chat_query(state);
if ((argc = smtpd_token(vstring_str(state->buffer), &argv)) == 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
smtpd_chat_reply(state, "500 Error: bad syntax");
state->error_count++;
continue;
}
for (cmdp = smtpd_cmd_table; cmdp->name != 0; cmdp++)
if (strcasecmp(argv[0].strval, cmdp->name) == 0)
break;
if (cmdp->name == 0) {
smtpd_chat_reply(state, "502 Error: command not implemented");
state->error_mask |= MAIL_ERROR_PROTOCOL;
state->error_count++;
continue;
}
if (state->access_denied && cmdp->action != quit_cmd) {
smtpd_chat_reply(state, "%s", state->access_denied);
state->error_count++;
continue;
}
state->where = cmdp->name;
if (cmdp->action(state, argc, argv) != 0)
state->error_count++;
if ((cmdp->flags & SMTPD_CMD_FLAG_LIMIT)
&& state->junk_cmds++ > var_smtpd_junk_cmd_limit)
state->error_count++;
if (cmdp->action == quit_cmd)
break;
}
break;
}
/*
* Log abnormal session termination, in case postmaster notification has
* been turned off. In the log, indicate the last recognized state before
* things went wrong. Don't complain about clients that go away without
* sending QUIT.
*/
if (state->reason && state->where && strcmp(state->where, SMTPD_AFTER_DOT))
msg_info("%s after %s from %s[%s]",
state->reason, state->where, state->name, state->addr);
/*
* Notify the postmaster if there were errors but no message was
* collected. This usually indicates a client configuration problem, or
* that someone is trying nasty things. Either is significant enough to
* bother the postmaster. XXX Can't report problems when running in
* stand-alone mode: postmaster notices require availability of the
* cleanup service.
*/
if (state->history != 0 && state->client != VSTREAM_IN
&& (state->error_mask & state->notify_mask))
smtpd_chat_notify(state);
/*
* Cleanup whatever information the client gave us during the SMTP
* dialog.
*/
helo_reset(state);
mail_reset(state);
rcpt_reset(state);
smtpd_chat_reset(state);
}
/* smtpd_service - service one client */
static void smtpd_service(VSTREAM *stream, char *unused_service, char **argv)
{
SMTPD_STATE state;
/*
* Sanity check. This service takes no command-line arguments.
*/
if (argv[0])
msg_fatal("unexpected command-line argument: %s", argv[0]);
/*
* This routine runs when a client has connected to our network port, or
* when the smtp server is run in stand-alone mode (input from pipe).
*
* Look up and sanitize the peer name, then initialize some connection-
* specific state. When the name service is hosed, hostname lookup will
* take a while. This is why I always run a local name server on critical
* machines.
*/
smtpd_state_init(&state, stream);
/*
* See if we need to turn on verbose logging for this client.
*/
debug_peer_check(state.name, state.addr);
/*
* See if we want to talk to this client at all. Then, log the connection
* event.
*/
if (var_smtpd_delay_reject == 0
&& (state.access_denied = smtpd_check_client(&state)) != 0) {
smtpd_chat_reply(&state, "%s", state.access_denied);
} else {
smtpd_chat_reply(&state, "220 %s", var_smtpd_banner);
msg_info("connect from %s[%s]", state.name, state.addr);
}
/*
* Provide the SMTP service.
*/
smtpd_proto(&state);
/*
* After the client has gone away, clean up whatever we have set up at
* connection time.
*/
msg_info("disconnect from %s[%s]", state.name, state.addr);
smtpd_state_reset(&state);
debug_peer_restore();
}
/* smtpd_cleanup - stand-alone mode queue file cleanup */
static void smtpd_cleanup(void)
{
char *myname = "smtpd_cleanup";
/*
* This routine is called by the run-time error handler, right before
* program exit.
*/
if (smtpd_path) {
if (remove(smtpd_path))
msg_warn("%s: remove %s: %m", myname, smtpd_path);
else if (msg_verbose)
msg_info("%s: remove %s", myname, smtpd_path);
smtpd_path = 0;
}
}
/* smtpd_sig - signal handler */
static void smtpd_sig(int sig)
{
smtpd_cleanup();
exit(sig);
}
/* pre_accept - see if tables have changed */
static void pre_accept(char *unused_name, char **unused_argv)
{
if (dict_changed()) {
msg_info("lookup table has changed -- exiting");
exit(0);
}
}
/* post_jail_init - post-jail initialization */
static void post_jail_init(char *unused_name, char **unused_argv)
{
/*
* Set up signal handlers so that we clean up in stand-alone mode.
*/
signal(SIGHUP, smtpd_sig);
signal(SIGINT, smtpd_sig);
signal(SIGQUIT, smtpd_sig);
signal(SIGTERM, smtpd_sig);
}
/* pre_jail_init - pre-jail initialization */
static void pre_jail_init(char *unused_name, char **unused_argv)
{
/*
* Initialize blacklist/etc. patterns before entering the chroot jail, in
* case they specify a filename pattern.
*/
smtpd_check_init();
debug_peer_init();
msg_cleanup(smtpd_cleanup);
}
/* main - the main program */
int main(int argc, char **argv)
{
static CONFIG_INT_TABLE int_table[] = {
VAR_SMTPD_RCPT_LIMIT, DEF_SMTPD_RCPT_LIMIT, &var_smtpd_rcpt_limit, 1, 0,
VAR_SMTPD_TMOUT, DEF_SMTPD_TMOUT, &var_smtpd_tmout, 1, 0,
VAR_SMTPD_SOFT_ERLIM, DEF_SMTPD_SOFT_ERLIM, &var_smtpd_soft_erlim, 1, 0,
VAR_SMTPD_HARD_ERLIM, DEF_SMTPD_HARD_ERLIM, &var_smtpd_hard_erlim, 1, 0,
VAR_QUEUE_MINFREE, DEF_QUEUE_MINFREE, &var_queue_minfree, 0, 0,
VAR_DEBUG_PEER_LEVEL, DEF_DEBUG_PEER_LEVEL, &var_debug_peer_level, 1, 0,
VAR_UNK_CLIENT_CODE, DEF_UNK_CLIENT_CODE, &var_unk_client_code, 0, 0,
VAR_BAD_NAME_CODE, DEF_BAD_NAME_CODE, &var_bad_name_code, 0, 0,
VAR_UNK_NAME_CODE, DEF_UNK_NAME_CODE, &var_unk_name_code, 0, 0,
VAR_UNK_ADDR_CODE, DEF_UNK_ADDR_CODE, &var_unk_addr_code, 0, 0,
VAR_RELAY_CODE, DEF_RELAY_CODE, &var_relay_code, 0, 0,
VAR_MAPS_RBL_CODE, DEF_MAPS_RBL_CODE, &var_maps_rbl_code, 0, 0,
VAR_ACCESS_MAP_CODE, DEF_ACCESS_MAP_CODE, &var_access_map_code, 0, 0,
VAR_REJECT_CODE, DEF_REJECT_CODE, &var_reject_code, 0, 0,
VAR_SMTPD_ERR_SLEEP, DEF_SMTPD_ERR_SLEEP, &var_smtpd_err_sleep, 0, 0,
VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code, 0, 0,
VAR_SMTPD_JUNK_CMD, DEF_SMTPD_JUNK_CMD, &var_smtpd_junk_cmd_limit, 1, 0,
0,
};
static CONFIG_BOOL_TABLE bool_table[] = {
VAR_HELO_REQUIRED, DEF_HELO_REQUIRED, &var_helo_required,
VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject,
VAR_STRICT_RFC821_ENV, DEF_STRICT_RFC821_ENV, &var_strict_rfc821_env,
VAR_DISABLE_VRFY_CMD, DEF_DISABLE_VRFY_CMD, &var_disable_vrfy_cmd,
VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route,
0,
};
static CONFIG_STR_TABLE str_table[] = {
VAR_RELAY_DOMAINS, DEF_RELAY_DOMAINS, &var_relay_domains, 0, 0,
VAR_SMTPD_BANNER, DEF_SMTPD_BANNER, &var_smtpd_banner, 1, 0,
VAR_DEBUG_PEER_LIST, DEF_DEBUG_PEER_LIST, &var_debug_peer_list, 0, 0,
VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0,
VAR_CLIENT_CHECKS, DEF_CLIENT_CHECKS, &var_client_checks, 0, 0,
VAR_HELO_CHECKS, DEF_HELO_CHECKS, &var_helo_checks, 0, 0,
VAR_MAIL_CHECKS, DEF_MAIL_CHECKS, &var_mail_checks, 0, 0,
VAR_RCPT_CHECKS, DEF_RCPT_CHECKS, &var_rcpt_checks, 0, 0,
VAR_ETRN_CHECKS, DEF_ETRN_CHECKS, &var_etrn_checks, 0, 0,
VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains, 0, 0,
VAR_ALWAYS_BCC, DEF_ALWAYS_BCC, &var_always_bcc, 0, 0,
VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0,
VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes, 0, 0,
VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0,
VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0,
VAR_VIRTUAL_MAPS, DEF_VIRTUAL_MAPS, &var_virtual_maps, 0, 0,
VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0,
VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0,
0,
};
/*
* Pass control to the single-threaded service skeleton.
*/
single_server_main(argc, argv, smtpd_service,
MAIL_SERVER_INT_TABLE, int_table,
MAIL_SERVER_STR_TABLE, str_table,
MAIL_SERVER_BOOL_TABLE, bool_table,
MAIL_SERVER_PRE_INIT, pre_jail_init,
MAIL_SERVER_POST_INIT, post_jail_init,
MAIL_SERVER_PRE_ACCEPT, pre_accept,
0);
}