/* 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
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_alias_maps;
+char *var_local_rcpt_maps;
/*
* Global state, for stand-alone mode queue file cleanup. When this is
/* extract_addr - extract address from rubble */
static char *extract_addr(SMTPD_STATE *state, SMTPD_TOKEN *arg,
- int allow_empty_addr)
+ int allow_empty_addr, int strict_rfc821)
{
char *myname = "extract_addr";
TOK822 *tree;
* Report trouble. Log a warning only if we are going to sleep+reject.
*/
if (naddr != 1
- || (var_strict_rfc821_env && (non_addr || *STR(arg->vstrval) != '<'))) {
+ || (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";
smtpd_chat_reply(state, "501 Bad address syntax");
return (-1);
}
- if ((err = extract_addr(state, argv + 2, PERMIT_EMPTY_ADDR)) != 0) {
+ 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);
smtpd_chat_reply(state, "501 Bad address syntax");
return (-1);
}
- if ((err = extract_addr(state, argv + 2, REJECT_EMPTY_ADDR)) != 0) {
+ 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);
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.
* 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");
}
if (argc > 2)
collapse_args(argc - 1, argv + 1);
- if ((err = extract_addr(state, argv + 1, REJECT_EMPTY_ADDR)) != 0) {
+ 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_rcpt(state, argv[1].strval);
-
- /*
- * End untokenize.
- */
- if (err != 0) {
+ if (SMTPD_STAND_ALONE(state) == 0
+ && (err = smtpd_check_rcpt(state, argv[1].strval)) != 0) {
+ smtpd_chat_reply(state, "%s", err);
+ return (-1);
+ }
+ if ((err = smtpd_check_rcptmap(state, argv[1].strval)) != 0) {
smtpd_chat_reply(state, "%s", err);
return (-1);
}
- smtpd_chat_reply(state, "252 Send mail to find out");
+ smtpd_chat_reply(state, "250 <%s>", argv[1].strval);
return (0);
}
typedef struct SMTPD_CMD {
char *name;
int (*action) (SMTPD_STATE *, int, SMTPD_TOKEN *);
-} SMTPD_CMD;
+} SMTPD_CMD;
static SMTPD_CMD smtpd_cmd_table[] = {
"HELO", helo_cmd,
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,
0,
};
static CONFIG_STR_TABLE str_table[] = {
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_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 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_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
0,
};
/* Allow the request when the local mail system is mail exchanger
/* for the recipient domain (this includes the case where the local
/* system is the final destination).
-/* .IP permit_recipient_map maptype:mapname
-/* Permit the request when the recipient address matches the named
-/* table. Lookups are done in the same way as with virtual and
-/* canonical maps.
/* .IP restriction_classes
/* Defines a list of parameter names, each parameter being a list
/* of restrictions that can be used anywhere a restriction is legal.
static VSTRING *query;
static VSTRING *error_text;
+ /*
+ * Pre-opened SMTP recipient maps.
+ */
+static MAPS *local_rcpt_maps;
+static MAPS *rcpt_canon_maps;
+static MAPS *canonical_maps;
+static MAPS *virtual_maps;
+
/*
* Pre-opened access control lists.
*/
-static DOMAIN_LIST *relay_domains;
-static NAMADR_LIST *mynetworks;
+static DOMAIN_LIST *relay_domains;
+static NAMADR_LIST *mynetworks;
/*
* Pre-parsed restriction lists.
*/
-static ARGV *client_restrctions;
+static ARGV *client_restrctions;
static ARGV *helo_restrctions;
static ARGV *mail_restrctions;
static ARGV *rcpt_restrctions;
static ARGV *etrn_restrctions;
-static HTABLE *smtpd_rest_classes;
-
-static HTABLE *smtpd_rcpt_maps;
+static HTABLE *smtpd_rest_classes;
/*
* The routine that recursively applies restrictions.
mynetworks = namadr_list_init(var_mynetworks);
relay_domains = domain_list_init(var_relay_domains);
+ /*
+ * Pre-parse and pre-open the recipient maps.
+ */
+ local_rcpt_maps = maps_create(VAR_LOCAL_RCPT_MAPS, var_local_rcpt_maps,
+ DICT_FLAG_LOCK);
+ rcpt_canon_maps = maps_create(VAR_RCPT_CANON_MAPS, var_rcpt_canon_maps,
+ DICT_FLAG_LOCK);
+ canonical_maps = maps_create(VAR_CANONICAL_MAPS, var_canonical_maps,
+ DICT_FLAG_LOCK);
+ virtual_maps = maps_create(VAR_VIRTUAL_MAPS, var_virtual_maps,
+ DICT_FLAG_LOCK);
+
/*
* Reply is used as a cache for resolved addresses, and error_text is
* used for returning error responses.
htable_enter("check_relay_domains",
smtpd_check_parse("permit_mydomain reject_unauth_destination"));
#endif
-
- /*
- * Other one-off initializations.
- */
- smtpd_rcpt_maps = htable_create(1);
}
/* smtpd_check_reject - do the boring things that must be done */
-static int smtpd_check_reject(SMTPD_STATE * state, int error_class,
+static int smtpd_check_reject(SMTPD_STATE *state, int error_class,
char *format,...)
{
va_list ap;
/* reject_unknown_client - fail if client hostname is unknown */
-static int reject_unknown_client(SMTPD_STATE * state)
+static int reject_unknown_client(SMTPD_STATE *state)
{
char *myname = "reject_unknown_client";
/* permit_mynetworks - succeed if client is in a trusted network */
-static int permit_mynetworks(SMTPD_STATE * state)
+static int permit_mynetworks(SMTPD_STATE *state)
{
char *myname = "permit_mynetworks";
/* reject_invalid_hostaddr - fail if host address is incorrect */
-static int reject_invalid_hostaddr(SMTPD_STATE * state, char *addr,
+static int reject_invalid_hostaddr(SMTPD_STATE *state, char *addr,
char *reply_name, char *reply_class)
{
char *myname = "reject_invalid_hostaddr";
/* reject_invalid_hostname - fail if host/domain syntax is incorrect */
-static int reject_invalid_hostname(SMTPD_STATE * state, char *name,
+static int reject_invalid_hostname(SMTPD_STATE *state, char *name,
char *reply_name, char *reply_class)
{
char *myname = "reject_invalid_hostname";
/* reject_non_fqdn_hostname - fail if host name is not in fqdn form */
-static int reject_non_fqdn_hostname(SMTPD_STATE * state, char *name,
+static int reject_non_fqdn_hostname(SMTPD_STATE *state, char *name,
char *reply_name, char *reply_class)
{
char *myname = "reject_non_fqdn_hostname";
/* reject_unknown_hostname - fail if name has no A or MX record */
-static int reject_unknown_hostname(SMTPD_STATE * state, char *name,
+static int reject_unknown_hostname(SMTPD_STATE *state, char *name,
char *reply_name, char *reply_class)
{
char *myname = "reject_unknown_hostname";
/* reject_unknown_mailhost - fail if name has no A or MX record */
-static int reject_unknown_mailhost(SMTPD_STATE * state, char *name,
+static int reject_unknown_mailhost(SMTPD_STATE *state, char *name,
char *reply_name, char *reply_class)
{
char *myname = "reject_unknown_mailhost";
/* check_relay_domains - OK/FAIL for message relaying */
-static int check_relay_domains(SMTPD_STATE * state, char *recipient,
+static int check_relay_domains(SMTPD_STATE *state, char *recipient,
char *reply_name, char *reply_class)
{
char *myname = "check_relay_domains";
/* reject_unauth_destination - FAIL for message relaying */
-static int reject_unauth_destination(SMTPD_STATE * state, char *recipient)
+static int reject_unauth_destination(SMTPD_STATE *state, char *recipient)
{
char *myname = "reject_unauth_destination";
char *domain;
/* reject_unauth_pipelining - reject improper use of SMTP command pipelining */
-static int reject_unauth_pipelining(SMTPD_STATE * state)
+static int reject_unauth_pipelining(SMTPD_STATE *state)
{
char *myname = "reject_unauth_pipelining";
/* permit_mx_backup - permit use of me as MX backup for recipient domain */
-static int permit_mx_backup(SMTPD_STATE * unused_state, const char *recipient)
+static int permit_mx_backup(SMTPD_STATE *unused_state, const char *recipient)
{
char *myname = "permit_mx_backup";
char *domain;
/* reject_non_fqdn_address - fail if address is not in fqdn form */
-static int reject_non_fqdn_address(SMTPD_STATE * state, char *addr,
+static int reject_non_fqdn_address(SMTPD_STATE *state, char *addr,
char *reply_name, char *reply_class)
{
char *myname = "reject_non_fqdn_address";
/* reject_unknown_address - fail if address does not resolve */
-static int reject_unknown_address(SMTPD_STATE * state, char *addr,
+static int reject_unknown_address(SMTPD_STATE *state, char *addr,
char *reply_name, char *reply_class)
{
char *myname = "reject_unknown_address";
return (reject_unknown_mailhost(state, domain, reply_name, reply_class));
}
-/* permit_rcpt_map - permit if recipient address matches rewriting table */
-
-static int permit_rcpt_map(char *table, char *reply_name)
-{
- char *myname = "permit_rcpt_map";
- char *domain;
-
- MAPS *map;
- DICT *dict;
-
- if (msg_verbose)
- msg_info("%s: %s %s", myname, table, reply_name);
-
- /*
- * Resolve the address.
- */
- canon_addr_internal(query, reply_name);
- resolve_clnt_query(STR(query), &reply);
-
- /*
- * Skip non-DNS forms. Skip non-local numerical forms.
- */
- if ((domain = strrchr(STR(reply.recipient), '@')) == 0)
- return (SMTPD_CHECK_DUNNO);
- domain += 1;
- if (domain[0] == '#' || domain[0] == '[')
- if (!match_any_local_transport(STR(reply.transport)))
- return (SMTPD_CHECK_DUNNO);
-
- /*
- * Look up the name in the specified table, using the usual magic of
- * canonical and virtual maps. Be sure this map was declared in a main.cf
- * restriction or restriction class. The maps must be opened before the
- * process enters a chroot jail.
- *
- * XXX What follows is a kludge to wrap up the recipient map in a form
- * usable by mail_addr_find(). Perhaps we should have a mail_addr_find()
- * interface that will search just one map instead of a list of maps.
- */
- if ((map = (MAPS *) htable_find(smtpd_rcpt_maps, table)) == 0) {
- if ((dict = dict_handle(table)) == 0)
- msg_panic("%s: dictionary not found: %s", myname, table);
- map = maps_create("rcpt_map", "", DICT_FLAG_LOCK);
- maps_append(map, table, dict);
- htable_enter(smtpd_rcpt_maps, table, (char *) map);
- }
-#define TOSS_THE_EXTENSION ((char **) 0)
-
- if (mail_addr_find(map, STR(reply.recipient), TOSS_THE_EXTENSION) != 0)
- return (SMTPD_CHECK_OK);
-
- return (SMTPD_CHECK_DUNNO);
-}
-
/* check_table_result - translate table lookup result into pass/reject */
-static int check_table_result(SMTPD_STATE * state, char *table,
+static int check_table_result(SMTPD_STATE *state, char *table,
const char *value, const char *datum,
char *reply_name, char *reply_class,
char *def_acl)
{
char *myname = "check_table_result";
int code;
- ARGV *restrictions;
+ ARGV *restrictions;
int status;
if (msg_verbose)
/* check_access - table lookup without substring magic */
-static int check_access(SMTPD_STATE * state, char *table, char *name, int flags,
+static int check_access(SMTPD_STATE *state, char *table, char *name, int flags,
char *reply_name, char *reply_class, char *def_acl)
{
char *myname = "check_access";
/* check_domain_access - domainname-based table lookup */
-static int check_domain_access(SMTPD_STATE * state, char *table,
+static int check_domain_access(SMTPD_STATE *state, char *table,
char *domain, int flags,
char *reply_name, char *reply_class,
char *def_acl)
/* check_addr_access - address-based table lookup */
-static int check_addr_access(SMTPD_STATE * state, char *table,
+static int check_addr_access(SMTPD_STATE *state, char *table,
char *address, int flags,
char *reply_name, char *reply_class,
char *def_acl)
/* check_namadr_access - OK/FAIL based on host name/address lookup */
-static int check_namadr_access(SMTPD_STATE * state, char *table,
+static int check_namadr_access(SMTPD_STATE *state, char *table,
char *name, char *addr, int flags,
char *reply_name, char *reply_class,
char *def_acl)
/* check_mail_access - OK/FAIL based on mail address lookup */
-static int check_mail_access(SMTPD_STATE * state, char *table, char *addr,
+static int check_mail_access(SMTPD_STATE *state, char *table, char *addr,
char *reply_name, char *reply_class,
char *def_acl)
{
/* reject_maps_rbl - reject if client address in real-time blackhole list */
-static int reject_maps_rbl(SMTPD_STATE * state)
+static int reject_maps_rbl(SMTPD_STATE *state)
{
char *myname = "reject_maps_rbl";
ARGV *octets = argv_split(state->addr, ".");
/* generic_checks - generic restrictions */
-static int generic_checks(SMTPD_STATE * state, ARGV *restrictions,
+static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
char *reply_name, char *reply_class, char *def_acl)
{
char *myname = "generic_checks";
if (state->recipient)
status = reject_non_fqdn_address(state, state->recipient,
state->recipient, SMTPD_NAME_RECIPIENT);
- } else if (is_map_command(name, PERMIT_RCPT_MAP, &cpp)) {
- if (state->recipient)
- status = permit_rcpt_map(*cpp, state->recipient);
}
/*
/* smtpd_check_client - validate client name or address */
-char *smtpd_check_client(SMTPD_STATE * state)
+char *smtpd_check_client(SMTPD_STATE *state)
{
int status;
/* smtpd_check_helo - validate HELO hostname */
-char *smtpd_check_helo(SMTPD_STATE * state, char *helohost)
+char *smtpd_check_helo(SMTPD_STATE *state, char *helohost)
{
int status;
char *saved_helo;
/* smtpd_check_mail - validate sender address, driver */
-char *smtpd_check_mail(SMTPD_STATE * state, char *sender)
+char *smtpd_check_mail(SMTPD_STATE *state, char *sender)
{
int status;
char *saved_sender;
/* smtpd_check_rcpt - validate recipient address, driver */
-char *smtpd_check_rcpt(SMTPD_STATE * state, char *recipient)
+char *smtpd_check_rcpt(SMTPD_STATE *state, char *recipient)
{
int status;
char *saved_recipient;
/* smtpd_check_etrn - validate ETRN request */
-char *smtpd_check_etrn(SMTPD_STATE * state, char *domain)
+char *smtpd_check_etrn(SMTPD_STATE *state, char *domain)
{
int status;
char *saved_etrn_name;
SMTPD_CHECK_ETRN_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
+/* smtpd_check_rcptmap - permit if recipient address matches lookup table */
+
+char *smtpd_check_rcptmap(SMTPD_STATE *state, char *recipient)
+{
+ char *myname = "smtpd_check_rcptmap";
+ char *saved_recipient;
+ char *domain;
+
+ /*
+ * XXX This module does a lot of unnecessary guessing. The SMTP server
+ * (and presumably, pickup daemon) should run the envelopes through a
+ * rewriting service that does all the canonical and virtual mapping.
+ */
+ if (msg_verbose)
+ msg_info("%s: %s", myname, recipient);
+
+ /*
+ * Minor kluge so that we can delegate work to the generic routine and so
+ * that we can syslog the recipient with the reject messages.
+ */
+ SMTPD_CHECK_PUSH(saved_recipient, state->recipient, recipient);
+
+ /*
+ * Resolve the address.
+ */
+ canon_addr_internal(query, recipient);
+ resolve_clnt_query(STR(query), &reply);
+
+ /*
+ * Skip non-DNS forms. Skip non-local numerical forms.
+ */
+ if ((domain = strrchr(STR(reply.recipient), '@')) == 0)
+ SMTPD_CHECK_RCPT_RETURN(0);
+ domain += 1;
+ if (domain[0] == '#' || domain[0] == '[')
+ if (!resolve_local(domain))
+ SMTPD_CHECK_RCPT_RETURN(0);
+
+ /*
+ * Reject mail to unknown addresses in domains that match $mydestination
+ * or $inet_interfaces (Postfix local). Reject mail to unknown addresses
+ * in Postfix virtual domains (Postfix virtual). Accept mail to other
+ * domains. Toss any extension information found by the lookup routines.
+ */
+#define NOP ((char **) 0)
+
+ if (resolve_local(domain)) {
+ if (*var_local_rcpt_maps
+ && !mail_addr_find(rcpt_canon_maps, STR(reply.recipient), NOP)
+ && !mail_addr_find(canonical_maps, STR(reply.recipient), NOP)
+ && !mail_addr_find(local_rcpt_maps, STR(reply.recipient), NOP)) {
+ (void) smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
+ "550 <%s>: User unknown", recipient);
+ SMTPD_CHECK_RCPT_RETURN(STR(error_text));
+ }
+ } else {
+ if (*var_virtual_maps
+ && !mail_addr_find(rcpt_canon_maps, STR(reply.recipient), NOP)
+ && !mail_addr_find(canonical_maps, STR(reply.recipient), NOP)
+ && !mail_addr_find(virtual_maps, STR(reply.recipient), NOP)
+ && maps_find(virtual_maps, domain, 0)) {
+ (void) smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
+ "550 <%s>: User unknown", recipient);
+ SMTPD_CHECK_RCPT_RETURN(STR(error_text));
+ }
+ }
+ SMTPD_CHECK_RCPT_RETURN(0);
+}
+
/* smtpd_check_size - check optional SIZE parameter value */
-char *smtpd_check_size(SMTPD_STATE * state, off_t size)
+char *smtpd_check_size(SMTPD_STATE *state, off_t size)
{
char *myname = "smtpd_check_size";
struct fsspace fsbuf;
char *name;
char *defval;
char **target;
-} STRING_TABLE;
+} STRING_TABLE;
static STRING_TABLE string_table[] = {
VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains,
char *name;
int defval;
int *target;
-} INT_TABLE;
+} INT_TABLE;
int var_unk_client_code;
int var_bad_name_code;
/* resolve_clnt_init - initialize reply */
-void resolve_clnt_init(RESOLVE_REPLY * reply)
+void resolve_clnt_init(RESOLVE_REPLY *reply)
{
reply->transport = vstring_alloc(100);
reply->nexthop = vstring_alloc(100);
/* canon_addr_internal - stub */
-VSTRING *canon_addr_internal(VSTRING * result, const char *addr)
+VSTRING *canon_addr_internal(VSTRING *result, const char *addr)
{
if (addr == STR(result))
msg_panic("canon_addr_internal: result clobbers input");
/* resolve_clnt_query - stub */
-void resolve_clnt_query(const char *addr, RESOLVE_REPLY * reply)
+void resolve_clnt_query(const char *addr, RESOLVE_REPLY *reply)
{
if (addr == STR(reply->recipient))
msg_panic("resolve_clnt_query: result clobbers input");
/* smtpd_chat_reset - stub */
-void smtpd_chat_reset(SMTPD_STATE * unused_state)
+void smtpd_chat_reset(SMTPD_STATE *unused_state)
{
}