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

2438 lines
69 KiB
C

/*++
/* NAME
/* smtpd_check 3
/* SUMMARY
/* SMTP client request filtering
/* SYNOPSIS
/* #include "smtpd.h"
/* #include "smtpd_check.h"
/*
/* void smtpd_check_init()
/*
/* char *smtpd_check_client(state)
/* SMTPD_STATE *state;
/*
/* char *smtpd_check_helo(state, helohost)
/* SMTPD_STATE *state;
/* char *helohost;
/*
/* char *smtpd_check_mail(state, sender)
/* SMTPD_STATE *state;
/* char *sender;
/*
/* char *smtpd_check_rcpt(state, recipient)
/* SMTPD_STATE *state;
/* char *recipient;
/*
/* char *smtpd_check_etrn(state, recipient)
/* SMTPD_STATE *state;
/* char *recipient;
/* DESCRIPTION
/* This module implements additional checks on SMTP client requests.
/* A client request is validated in the context of the session state.
/* The result is either an error response (including the numerical
/* code) or the result is a null pointer in case of success.
/*
/* smtpd_check_init() initializes. This function should be called
/* once during the process life time.
/*
/* Each of the following routines scrutinizes the argument passed to
/* an SMTP command such as HELO, MAIL FROM, RCPT TO, or scrutinizes
/* the initial client connection request. The administrator can
/* specify what restrictions apply.
/*
/* Restrictions are specified via configuration parameters named
/* \fIsmtpd_{client,helo,sender,recipient}_restrictions.\fR Each
/* configuration parameter specifies a list of zero or more
/* restrictions that are applied in the order as specified.
/*
/* Restrictions that can appear in some or all restriction
/* lists:
/* .IP reject
/* .IP permit
/* Reject or permit the request unconditionally. This is to be used
/* at the end of a restriction list in order to make the default
/* action explicit.
/* .IP reject_unknown_client
/* Reject the request when the client hostname could not be found.
/* The \fIunknown_client_reject_code\fR configuration parameter
/* specifies the reject status code (default: 450).
/* .IP permit_mynetworks
/* Allow the request when the client address matches the \fImynetworks\fR
/* configuration parameter.
/* .IP maptype:mapname
/* Meaning depends on context: client name/address, helo name, sender
/* or recipient address.
/* Perform a lookup in the specified access table. Reject the request
/* when the lookup result is REJECT or when the result begins with a
/* 4xx or 5xx status code. Other numerical status codes are not
/* permitted. Allow the request otherwise. The
/* \fIaccess_map_reject_code\fR configuration parameter specifies the
/* reject status code (default: 554).
/* .IP "check_client_access maptype:mapname"
/* Look up the client host name or any of its parent domains, or
/* the client address or any network obtained by stripping octets
/* from the address.
/* .IP "check_helo_access maptype:mapname"
/* Look up the HELO/EHLO hostname or any of its parent domains.
/* .IP "check_sender_access maptype:mapname"
/* Look up the resolved sender address, any parent domains of the
/* resolved sender address domain, or the localpart@.
/* .IP "check_recipient_access maptype:mapname"
/* Look up the resolved recipient address in the named access table,
/* any parent domains of the recipient domain, and the localpart@.
/* .IP reject_maps_rbl
/* Look up the client network address in the real-time blackhole
/* DNS zones below the domains listed in the "maps_rbl_domains"
/* configuration parameter. The \fImaps_rbl_reject_code\fR
/* configuration parameter specifies the reject status code
/* (default: 554).
/* .IP permit_naked_ip_address
/* Permit the use of a naked IP address (without enclosing [])
/* in HELO/EHLO commands.
/* This violates the RFC. You must enable this for some popular
/* PC mail clients.
/* .IP reject_non_fqdn_hostname
/* .IP reject_non_fqdn_sender
/* .IP reject_non_fqdn_recipient
/* Require that the HELO, MAIL FROM or RCPT TO commands specify
/* a fully-qualified domain name. The non_fqdn_reject_code parameter
/* specifies the error code (default: 504).
/* .IP reject_invalid_hostname
/* Reject the request when the HELO/EHLO hostname does not satisfy RFC
/* requirements. The underscore is considered a legal hostname character,
/* and so is a trailing dot.
/* The \fIinvalid_hostname_reject_code\fR configuration parameter
/* specifies the reject status code (default:501).
/* .IP reject_unknown_hostname
/* Reject the request when the HELO/EHLO hostname has no A or MX record.
/* The \fIunknown_hostname_reject_code\fR configuration
/* parameter specifies the reject status code (default: 450).
/* .IP reject_unknown_sender_domain
/* Reject the request when the resolved sender address has no
/* DNS A or MX record.
/* The \fIunknown_address_reject_code\fR configuration parameter
/* specifies the reject status code (default: 450).
/* .IP reject_unknown_recipient_domain
/* Reject the request when the resolved recipient address has no
/* DNS A or MX record.
/* The \fIunknown_address_reject_code\fR configuration parameter
/* specifies the reject status code (default: 450).
/* .IP check_relay_domains
/* Allow the request when either the client hostname or the resolved
/* recipient domain matches the \fIrelay_domains\fR configuration
/* parameter or a subdomain therereof, or when the destination somehow
/* resolves locally ($inet_interfaces, $mydestination or $virtual_maps).
/* Reject the request otherwise.
/* The \fIrelay_domains_reject_code\fR configuration parameter specifies
/* the reject status code (default: 554).
/* .IP permit_auth_destination
/* Permit the request when the resolved recipient domain matches the
/* \fIrelay_domains\fR configuration parameter or a subdomain therereof,
/* or when the destination somehow resolves locally ($inet_interfaces,
/* $mydestination or $virtual_maps).
/* .IP reject_unauth_destination
/* Reject the request when the resolved recipient domain does not match
/* the \fIrelay_domains\fR configuration parameter or a subdomain
/* therereof, and when the destination does not somehow resolve locally
/* ($inet_interfaces, $mydestination or $virtual_maps).
/* Same error code as check_relay_domains.
/* .IP reject_unauth_pipelining
/* Reject the request when the client has already sent the next request
/* without being told that the server implements SMTP command pipelining.
/* .IP permit_mx_backup
/* 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 restriction_classes
/* Defines a list of parameter names, each parameter being a list
/* of restrictions that can be used anywhere a restriction is legal.
/* .PP
/* smtpd_check_client() validates the client host name or address.
/* Relevant configuration parameters:
/* .IP smtpd_client_restrictions
/* Restrictions on the names or addresses of clients that may connect
/* to this SMTP server.
/* .PP
/* smtpd_check_helo() validates the hostname provided with the
/* HELO/EHLO commands. Relevant configuration parameters:
/* .IP smtpd_helo_restrictions
/* Restrictions on the hostname that is sent with the HELO/EHLO
/* command.
/* .PP
/* smtpd_check_mail() validates the sender address provided with
/* a MAIL FROM request. Relevant configuration parameters:
/* .IP smtpd_sender_restrictions
/* Restrictions on the sender address that is sent with the MAIL FROM
/* command.
/* .PP
/* smtpd_check_rcpt() validates the recipient address provided
/* with an RCPT TO request. Relevant configuration parameters:
/* .IP smtpd_recipient_restrictions
/* Restrictions on the recipient address that is sent with the RCPT
/* TO command.
/* .PP
/* smtpd_check_etrn() validates the domain name provided with the
/* ETRN command, and other client-provided information. Relevant
/* configuration parameters:
/* .IP smtpd_etrn_restrictions
/* Restrictions on the hostname that is sent with the HELO/EHLO
/* command.
/* .PP
/* smtpd_check_size() checks if a message with the given size can
/* be received (zero means that the message size is unknown). The
/* message is rejected when:
/* .IP \(bu
/* The message size exceeds the non-zero bound specified with the
/* \fImessage_size_limit\fR configuration parameter. This is a
/* permanent error.
/* .IP \(bu
/* The message would cause the available queue file system space
/* to drop below the bound specified with the \fImin_queue_free\fR
/* configuration parameter. This is a temporary error.
/* .IP \(bu
/* The message would use up more than half the available queue file
/* system space. This is a temporary error.
/* .PP
/* Arguments:
/* .IP name
/* The client hostname, or \fIunknown\fR.
/* .IP addr
/* The client address.
/* .IP helohost
/* The hostname given with the HELO command.
/* .IP sender
/* The sender address given with the MAIL FROM command.
/* .IP recipient
/* The recipient address given with the RCPT TO or VRFY command.
/* .IP size
/* The message size given with the MAIL FROM command (zero if unknown).
/* BUGS
/* Policies like these should not be hard-coded in C, but should
/* be user-programmable instead.
/* SEE ALSO
/* namadr_list(3) host access control
/* domain_list(3) domain access control
/* fsspace(3) free file system space
/* 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 <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <netdb.h>
#include <setjmp.h>
#include <stdlib.h>
#include <unistd.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif
/* Utility library. */
#include <msg.h>
#include <vstring.h>
#include <split_at.h>
#include <fsspace.h>
#include <stringops.h>
#include <valid_hostname.h>
#include <argv.h>
#include <mymalloc.h>
#include <dict.h>
#include <htable.h>
/* DNS library. */
#include <dns.h>
/* Global library. */
#include <namadr_list.h>
#include <domain_list.h>
#include <mail_params.h>
#include <canon_addr.h>
#include <resolve_clnt.h>
#include <mail_error.h>
#include <resolve_local.h>
#include <own_inet_addr.h>
#include <mail_conf.h>
#include <maps.h>
#include <mail_addr_find.h>
/* Application-specific. */
#include "smtpd.h"
#include "smtpd_check.h"
/*
* Eject seat in case of parsing problems.
*/
static jmp_buf smtpd_check_buf;
/*
* Results of restrictions.
*/
#define SMTPD_CHECK_DUNNO 0 /* indifferent */
#define SMTPD_CHECK_OK 1 /* explicitly permit */
#define SMTPD_CHECK_REJECT 2 /* explicitly reject */
/*
* Intermediate results. These are static to avoid unnecessary stress on the
* memory manager routines.
*/
static RESOLVE_REPLY reply;
static VSTRING *query;
static VSTRING *error_text;
/*
* Pre-opened SMTP recipient maps so we can reject mail for unknown users.
* XXX This does not belong here and will eventually become part of the
* trivial-rewrite resolver.
*/
static MAPS *local_rcpt_maps;
static MAPS *rcpt_canon_maps;
static MAPS *canonical_maps;
static MAPS *virtual_maps;
static MAPS *relocated_maps;
/*
* Pre-opened access control lists.
*/
static DOMAIN_LIST *relay_domains;
static NAMADR_LIST *mynetworks;
/*
* Pre-parsed restriction lists.
*/
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;
/*
* The routine that recursively applies restrictions.
*/
static int generic_checks(SMTPD_STATE *, ARGV *, char *, char *, char *);
/*
* Reject context.
*/
#define SMTPD_NAME_CLIENT "Client host"
#define SMTPD_NAME_HELO "Helo command"
#define SMTPD_NAME_SENDER "Sender address"
#define SMTPD_NAME_RECIPIENT "Recipient address"
#define SMTPD_NAME_ETRN "Etrn command"
/*
* YASLM.
*/
#define STR vstring_str
/* smtpd_check_parse - pre-parse restrictions */
static ARGV *smtpd_check_parse(const char *checks)
{
char *saved_checks = mystrdup(checks);
ARGV *argv = argv_alloc(1);
char *bp = saved_checks;
char *name;
/*
* Pre-parse the restriction list, and open any dictionaries that we
* encounter. Dictionaries must be opened before entering the chroot
* jail.
*/
while ((name = mystrtok(&bp, " \t\r\n,")) != 0) {
argv_add(argv, name, (char *) 0);
if (strchr(name, ':') && dict_handle(name) == 0)
dict_register(name, dict_open(name, O_RDONLY, DICT_FLAG_LOCK));
}
argv_terminate(argv);
/*
* Cleanup.
*/
myfree(saved_checks);
return (argv);
}
/* has_required - make sure required restriction is present */
static int has_required(ARGV *restrictions, char **required)
{
char **rest;
char **reqd;
ARGV *expansion;
/*
* Recursively check list membership.
*/
for (rest = restrictions->argv; *rest; rest++) {
for (reqd = required; *reqd; reqd++)
if (strcmp(*rest, *reqd) == 0)
return (1);
if ((expansion = (ARGV *) htable_find(smtpd_rest_classes, *rest)) != 0)
if (has_required(expansion, required))
return (1);
}
return (0);
}
/* fail_required - handle failure to use required restriction */
static void fail_required(char *name, char **required)
{
char *myname = "fail_required";
char **reqd;
VSTRING *example;
/*
* Sanity check.
*/
if (required[0] == 0)
msg_panic("%s: null required list", myname);
/*
* Go bust.
*/
example = vstring_alloc(10);
for (reqd = required; *reqd; reqd++)
vstring_sprintf_append(example, "%s ", *reqd);
msg_fatal("parameter \"%s\": specify at least one explicit instance of: %s",
name, STR(example));
}
/* smtpd_check_init - initialize once during process lifetime */
void smtpd_check_init(void)
{
char *saved_classes;
const char *name;
const char *value;
char *cp;
static char *rcpt_required[] = {
CHECK_RELAY_DOMAINS,
REJECT_UNAUTH_DEST,
REJECT_ALL,
0,
};
/*
* Pre-open access control lists before going to jail.
*/
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);
relocated_maps = maps_create(VAR_RELOCATED_MAPS, var_relocated_maps,
DICT_FLAG_LOCK);
/*
* Reply is used as a cache for resolved addresses, and error_text is
* used for returning error responses.
*/
resolve_clnt_init(&reply);
query = vstring_alloc(10);
error_text = vstring_alloc(10);
/*
* Pre-parse the restriction lists. At the same time, pre-open tables
* before going to jail.
*/
client_restrctions = smtpd_check_parse(var_client_checks);
helo_restrctions = smtpd_check_parse(var_helo_checks);
mail_restrctions = smtpd_check_parse(var_mail_checks);
rcpt_restrctions = smtpd_check_parse(var_rcpt_checks);
etrn_restrctions = smtpd_check_parse(var_etrn_checks);
/*
* Parse the pre-defined restriction classes.
*/
smtpd_rest_classes = htable_create(1);
if (*var_rest_classes) {
cp = saved_classes = mystrdup(var_rest_classes);
while ((name = mystrtok(&cp, " \t\r\n,")) != 0) {
if ((value = mail_conf_lookup_eval(name)) == 0 || *value == 0)
msg_fatal("restriction class `%s' needs a definition", name);
htable_enter(smtpd_rest_classes, name,
(char *) smtpd_check_parse(value));
}
myfree(saved_classes);
}
/*
* This is the place to specify definitions for complex restrictions such
* as check_relay_domains in terms of more elementary restrictions.
*/
#if 0
htable_enter(smtpd_rest_classes, "check_relay_domains",
smtpd_check_parse("permit_mydomain reject_unauth_destination"));
#endif
/*
* People screw up the relay restrictions too often. Require that they
* list at least one restriction that rejects mail by default.
*/
#ifndef TEST
if (!has_required(rcpt_restrctions, rcpt_required))
fail_required(VAR_RCPT_CHECKS, rcpt_required);
#endif
}
/* smtpd_check_reject - do the boring things that must be done */
static int smtpd_check_reject(SMTPD_STATE *state, int error_class,
char *format,...)
{
va_list ap;
/*
* Update the error class mask, and format the response. XXX What about
* multi-line responses? For now we cheat and send whitespace.
*/
state->error_mask |= error_class;
va_start(ap, format);
vstring_vsprintf(error_text, format, ap);
va_end(ap);
/*
* Validate the response, that is, the response must begin with a
* three-digit status code, and the first digit must be 4 or 5. If the
* response is bad, log a warning and send a generic response instead.
*/
if ((STR(error_text)[0] != '4' && STR(error_text)[0] != '5')
|| !ISDIGIT(STR(error_text)[1]) || !ISDIGIT(STR(error_text)[2])
|| ISDIGIT(STR(error_text)[3])) {
msg_warn("response code configuration error: %s", STR(error_text));
vstring_strcpy(error_text, "450 Service unavailable");
}
printable(STR(error_text), ' ');
/*
* Log what is happening. When the sysadmin discards policy violation
* postmaster notices, this may be the only trace left that service was
* rejected. Print the request, client name/address, and response.
*/
if (state->recipient && state->sender) {
msg_info("reject: %s from %s: %s; from=<%s> to=<%s>",
state->where, state->namaddr, STR(error_text),
state->sender, state->recipient);
} else if (state->recipient) {
msg_info("reject: %s from %s: %s; to=<%s>",
state->where, state->namaddr, STR(error_text),
state->recipient);
} else if (state->sender) {
msg_info("reject: %s from %s: %s; from=<%s>",
state->where, state->namaddr, STR(error_text),
state->sender);
} else {
msg_info("reject: %s from %s: %s",
state->where, state->namaddr, STR(error_text));
}
return (SMTPD_CHECK_REJECT);
}
/* reject_unknown_client - fail if client hostname is unknown */
static int reject_unknown_client(SMTPD_STATE *state)
{
char *myname = "reject_unknown_client";
if (msg_verbose)
msg_info("%s: %s %s", myname, state->name, state->addr);
if (strcasecmp(state->name, "unknown") == 0)
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d Client host rejected: cannot find your hostname, [%s]",
state->peer_code == 5 ?
var_unk_client_code : 450,
state->addr));
return (SMTPD_CHECK_DUNNO);
}
/* permit_mynetworks - succeed if client is in a trusted network */
static int permit_mynetworks(SMTPD_STATE *state)
{
char *myname = "permit_mynetworks";
if (msg_verbose)
msg_info("%s: %s %s", myname, state->name, state->addr);
if (namadr_list_match(mynetworks, state->name, state->addr))
return (SMTPD_CHECK_OK);
return (SMTPD_CHECK_DUNNO);
}
/* dup_if_truncate - save hostname and truncate if it ends in dot */
static char *dup_if_truncate(char *name)
{
int len;
char *result;
/*
* Truncate hostnames ending in dot but not dot-dot.
*/
if ((len = strlen(name)) > 1
&& name[len - 1] == '.'
&& name[len - 2] != '.') {
result = mystrndup(name, len - 1);
} else
result = name;
return (result);
}
/* reject_invalid_hostaddr - fail if host address is incorrect */
static int reject_invalid_hostaddr(SMTPD_STATE *state, char *addr,
char *reply_name, char *reply_class)
{
char *myname = "reject_invalid_hostaddr";
int len;
char *test_addr;
int stat;
if (msg_verbose)
msg_info("%s: %s", myname, addr);
if (addr[0] == '[' && (len = strlen(addr)) > 2 && addr[len - 1] == ']') {
test_addr = mystrndup(&addr[1], len - 2);
} else
test_addr = addr;
/*
* Validate the address.
*/
if (!valid_hostaddr(test_addr))
stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: invalid ip address",
var_bad_name_code, reply_name, reply_class);
else
stat = SMTPD_CHECK_DUNNO;
/*
* Cleanup.
*/
if (test_addr != addr)
myfree(test_addr);
return (stat);
}
/* reject_invalid_hostname - fail if host/domain syntax is incorrect */
static int reject_invalid_hostname(SMTPD_STATE *state, char *name,
char *reply_name, char *reply_class)
{
char *myname = "reject_invalid_hostname";
char *test_name;
int stat;
if (msg_verbose)
msg_info("%s: %s", myname, name);
/*
* Truncate hostnames ending in dot but not dot-dot.
*/
test_name = dup_if_truncate(name);
/*
* Validate the hostname.
*/
if (!valid_hostname(test_name))
stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: Invalid name",
var_bad_name_code, reply_name, reply_class);
else
stat = SMTPD_CHECK_DUNNO;
/*
* Cleanup.
*/
if (test_name != name)
myfree(test_name);
return (stat);
}
/* reject_non_fqdn_hostname - fail if host name is not in fqdn form */
static int reject_non_fqdn_hostname(SMTPD_STATE *state, char *name,
char *reply_name, char *reply_class)
{
char *myname = "reject_non_fqdn_hostname";
char *test_name;
int stat;
if (msg_verbose)
msg_info("%s: %s", myname, name);
/*
* Truncate hostnames ending in dot but not dot-dot.
*/
test_name = dup_if_truncate(name);
/*
* Validate the hostname.
*/
if (!valid_hostname(test_name) || !strchr(test_name, '.'))
stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: need fully-qualified hostname",
var_non_fqdn_code, reply_name, reply_class);
else
stat = SMTPD_CHECK_DUNNO;
/*
* Cleanup.
*/
if (test_name != name)
myfree(test_name);
return (stat);
}
/* reject_unknown_hostname - fail if name has no A or MX record */
static int reject_unknown_hostname(SMTPD_STATE *state, char *name,
char *reply_name, char *reply_class)
{
char *myname = "reject_unknown_hostname";
int dns_status;
if (msg_verbose)
msg_info("%s: %s", myname, name);
dns_status = dns_lookup_types(name, 0, (DNS_RR **) 0, (VSTRING *) 0,
(VSTRING *) 0, T_A, T_MX, 0);
if (dns_status != DNS_OK)
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: Host not found",
dns_status == DNS_NOTFOUND ?
var_unk_name_code : 450,
reply_name, reply_class));
return (SMTPD_CHECK_DUNNO);
}
/* reject_unknown_mailhost - fail if name has no A or MX record */
static int reject_unknown_mailhost(SMTPD_STATE *state, char *name,
char *reply_name, char *reply_class)
{
char *myname = "reject_unknown_mailhost";
int dns_status;
if (msg_verbose)
msg_info("%s: %s", myname, name);
dns_status = dns_lookup_types(name, 0, (DNS_RR **) 0, (VSTRING *) 0,
(VSTRING *) 0, T_A, T_MX, 0);
if (dns_status != DNS_OK)
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: Domain not found",
dns_status == DNS_NOTFOUND ?
var_unk_addr_code : 450,
reply_name, reply_class));
return (SMTPD_CHECK_DUNNO);
}
/* check_relay_domains - OK/FAIL for message relaying */
static int check_relay_domains(SMTPD_STATE *state, char *recipient,
char *reply_name, char *reply_class)
{
char *myname = "check_relay_domains";
static int permit_auth_destination(char *recipient);
if (msg_verbose)
msg_info("%s: %s", myname, recipient);
/*
* Permit if the client matches the relay_domains list.
*/
if (domain_list_match(relay_domains, state->name))
return (SMTPD_CHECK_OK);
/*
* Permit authorized destinations.
*/
if (permit_auth_destination(recipient) == SMTPD_CHECK_OK)
return (SMTPD_CHECK_OK);
/*
* Deny relaying between sites that both are not in relay_domains.
*/
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: Relay access denied",
var_relay_code, reply_name, reply_class));
}
/* permit_auth_destination - OK for message relaying */
static int permit_auth_destination(char *recipient)
{
char *myname = "permit_auth_destination";
char *domain;
if (msg_verbose)
msg_info("%s: %s", myname, recipient);
/*
* Resolve the address.
*/
canon_addr_internal(query, recipient);
resolve_clnt_query(STR(query), &reply);
lowercase(STR(reply.recipient));
/*
* Handle special case that is not supposed to happen.
*/
if ((domain = strrchr(STR(reply.recipient), '@')) == 0)
return (SMTPD_CHECK_OK);
domain += 1;
/*
* Permit final delivery: the destination matches mydestination or
* virtual_maps.
*/
if (resolve_local(domain)
|| (*var_virtual_maps && maps_find(virtual_maps, domain, 0)))
return (SMTPD_CHECK_OK);
/*
* Skip source-routed mail (uncertain destination).
*/
if (var_allow_untrust_route == 0 && (reply.flags & RESOLVE_FLAG_ROUTED))
return (SMTPD_CHECK_DUNNO);
/*
* Permit if the destination matches the relay_domains list.
*/
if (domain_list_match(relay_domains, domain))
return (SMTPD_CHECK_OK);
/*
* Skip when not matched
*/
return (SMTPD_CHECK_DUNNO);
}
/* reject_unauth_destination - FAIL for message relaying */
static int reject_unauth_destination(SMTPD_STATE *state, char *recipient)
{
char *myname = "reject_unauth_destination";
if (msg_verbose)
msg_info("%s: %s", myname, recipient);
/*
* Skip authorized destination.
*/
if (permit_auth_destination(recipient) == SMTPD_CHECK_OK)
return (SMTPD_CHECK_DUNNO);
/*
* Reject relaying to sites that are not listed in relay_domains.
*/
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: Relay access denied",
var_relay_code, recipient));
}
/* reject_unauth_pipelining - reject improper use of SMTP command pipelining */
static int reject_unauth_pipelining(SMTPD_STATE *state)
{
char *myname = "reject_unauth_pipelining";
if (msg_verbose)
msg_info("%s: %s", myname, state->where);
if (state->client != 0
&& SMTPD_STAND_ALONE(state) == 0
&& vstream_peek(state->client) > 0
&& strcasecmp(state->protocol, "ESMTP") != 0) {
return (smtpd_check_reject(state, MAIL_ERROR_PROTOCOL,
"503 Improper use of SMTP command pipelining"));
}
return (SMTPD_CHECK_DUNNO);
}
/* has_my_addr - see if this host name lists one of my network addresses */
static int has_my_addr(char *host)
{
char *myname = "has_my_addr";
struct in_addr addr;
char **cpp;
struct hostent *hp;
if (msg_verbose)
msg_info("%s: host %s", myname, host);
/*
* If we can't lookup the host, play safe and assume it is OK.
*/
#define YUP 1
#define NOPE 0
if ((hp = gethostbyname(host)) == 0) {
if (msg_verbose)
msg_info("%s: host %s: not found", myname, host);
return (YUP);
}
if (hp->h_addrtype != AF_INET || hp->h_length != sizeof(addr)) {
msg_warn("address type %d length %d for %s",
hp->h_addrtype, hp->h_length, host);
return (YUP);
}
for (cpp = hp->h_addr_list; *cpp; cpp++) {
memcpy((char *) &addr, *cpp, sizeof(addr));
if (msg_verbose)
msg_info("%s: addr %s", myname, inet_ntoa(addr));
if (own_inet_addr(&addr))
return (YUP);
}
if (msg_verbose)
msg_info("%s: host %s: no match", myname, host);
return (NOPE);
}
/* 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)
{
char *myname = "permit_mx_backup";
char *domain;
DNS_RR *mx_list;
DNS_RR *mx;
int dns_status;
if (msg_verbose)
msg_info("%s: %s", myname, recipient);
/*
* Resolve the address.
*/
canon_addr_internal(query, recipient);
resolve_clnt_query(STR(query), &reply);
lowercase(STR(reply.recipient));
/*
* If the destination is local, it is acceptable, because we are
* supposedly MX for our own address.
*/
if ((domain = strrchr(STR(reply.recipient), '@')) == 0)
return (SMTPD_CHECK_OK);
domain += 1;
if (resolve_local(domain)
|| (*var_virtual_maps && maps_find(virtual_maps, domain, 0)))
return (SMTPD_CHECK_OK);
if (msg_verbose)
msg_info("%s: not local: %s", myname, recipient);
/*
* Skip source-routed mail (uncertain destination).
*/
if (var_allow_untrust_route == 0 && (reply.flags & RESOLVE_FLAG_ROUTED))
return (SMTPD_CHECK_DUNNO);
/*
* Skip numerical forms that didn't match the local system.
*/
if (domain[0] == '#'
|| (domain[0] == '[' && domain[strlen(domain) - 1] == ']'))
return (SMTPD_CHECK_DUNNO);
/*
* Look up the list of MX host names for this domain. If no MX host is
* found, perhaps it is a CNAME for the local machine. Clients aren't
* supposed to send CNAMEs in SMTP commands, but it happens anyway. If we
* can't look up the destination, play safe and assume it is OK.
*/
dns_status = dns_lookup(domain, T_MX, 0, &mx_list,
(VSTRING *) 0, (VSTRING *) 0);
if (dns_status == DNS_NOTFOUND)
return (has_my_addr(domain) ? SMTPD_CHECK_OK : SMTPD_CHECK_DUNNO);
if (dns_status != DNS_OK)
return (SMTPD_CHECK_OK);
/*
* First, see if we match any of the MX host names listed. Only if no
* name match is found, go through the trouble of host address lookups.
*/
for (mx = mx_list; mx != 0; mx = mx->next) {
if (msg_verbose)
msg_info("%s: resolve hostname: %s", myname, (char *) mx->data);
if (resolve_local((char *) mx->data)) {
dns_rr_free(mx_list);
return (SMTPD_CHECK_OK);
}
}
/*
* Argh. Do further DNS lookups and match interface addresses.
*/
for (mx = mx_list; mx != 0; mx = mx->next) {
if (msg_verbose)
msg_info("%s: address lookup: %s", myname, (char *) mx->data);
if (has_my_addr((char *) mx->data)) {
dns_rr_free(mx_list);
return (SMTPD_CHECK_OK);
}
}
if (msg_verbose)
msg_info("%s: no match", myname);
dns_rr_free(mx_list);
return (SMTPD_CHECK_DUNNO);
}
/* reject_non_fqdn_address - fail if address is not in fqdn form */
static int reject_non_fqdn_address(SMTPD_STATE *state, char *addr,
char *reply_name, char *reply_class)
{
char *myname = "reject_non_fqdn_address";
char *domain;
char *test_dom;
int stat;
if (msg_verbose)
msg_info("%s: %s", myname, addr);
/*
* Locate the domain information.
*/
if ((domain = strrchr(addr, '@')) != 0)
domain++;
else
domain = "";
/*
* Skip forms that we can't handle yet.
*/
if (domain[0] == '#')
return (SMTPD_CHECK_DUNNO);
if (domain[0] == '[' && domain[strlen(domain) - 1] == ']')
return (SMTPD_CHECK_DUNNO);
/*
* Truncate names ending in dot but not dot-dot.
*/
test_dom = dup_if_truncate(domain);
/*
* Validate the domain.
*/
if (!*test_dom || !valid_hostname(test_dom) || !strchr(test_dom, '.'))
stat = smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: need fully-qualified address",
var_non_fqdn_code, reply_name, reply_class);
else
stat = SMTPD_CHECK_DUNNO;
/*
* Cleanup.
*/
if (test_dom != domain)
myfree(test_dom);
return (stat);
}
/* reject_unknown_address - fail if address does not resolve */
static int reject_unknown_address(SMTPD_STATE *state, char *addr,
char *reply_name, char *reply_class)
{
char *myname = "reject_unknown_address";
char *domain;
if (msg_verbose)
msg_info("%s: %s", myname, addr);
/*
* Resolve the address.
*/
canon_addr_internal(query, addr);
resolve_clnt_query(STR(query), &reply);
lowercase(STR(reply.recipient));
/*
* Skip local destinations and non-DNS forms.
*/
if ((domain = strrchr(STR(reply.recipient), '@')) == 0)
return (SMTPD_CHECK_DUNNO);
domain += 1;
if (resolve_local(domain)
|| (*var_virtual_maps && maps_find(virtual_maps, domain, 0)))
return (SMTPD_CHECK_DUNNO);
if (domain[0] == '#')
return (SMTPD_CHECK_DUNNO);
if (domain[0] == '[' && domain[strlen(domain) - 1] == ']')
return (SMTPD_CHECK_DUNNO);
/*
* Look up the name in the DNS.
*/
return (reject_unknown_mailhost(state, domain, reply_name, reply_class));
}
/* check_table_result - translate table lookup result into pass/reject */
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;
int status;
if (msg_verbose)
msg_info("%s: %s %s %s", myname, table, value, datum);
/*
* DUNNO means skip this table.
*/
if (strcasecmp(value, "DUNNO") == 0)
return (SMTPD_CHECK_DUNNO);
/*
* REJECT means NO. Generate a generic error response.
*/
if (strcasecmp(value, "REJECT") == 0)
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: Access denied",
var_access_map_code, reply_name, reply_class));
/*
* All-numeric result probably means OK - some out-of-band authentication
* mechanism uses this as time stamp.
*/
if (*value && value[strspn(value, "0123456789")] == 0)
return (SMTPD_CHECK_OK);
/*
* 4xx or 5xx means NO as well. smtpd_check_reject() will validate the
* response status code.
*/
if (ISDIGIT(value[0]) && ISDIGIT(value[1]) && ISDIGIT(value[2])) {
code = atoi(value);
while (ISDIGIT(*value) || ISSPACE(*value))
value++;
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: %s",
code, reply_name, reply_class, value));
}
/*
* OK or RELAY means YES.
*/
if (strcasecmp(value, "OK") == 0 || strcasecmp(value, "RELAY") == 0)
return (SMTPD_CHECK_OK);
/*
* Unfortunately, maps must be declared ahead of time so they can be
* opened before we go to jail. We could insist that the RHS can only
* contain a pre-defined restriction class name, but that would be too
* restrictive. Instead we warn if an access table references any map.
*
* XXX Don't use passwd files or address rewriting maps as access tables.
*/
if (strchr(value, ':') != 0) {
msg_warn("SMTPD access map %s has entry with lookup table: %s",
table, value);
msg_warn("do not specify lookup tables inside SMTPD access maps");
msg_warn("define a restriction class and specify its name instead");
longjmp(smtpd_check_buf, -1);
}
/*
* Don't get carried away with recursion.
*/
if (state->recursion++ > 100) {
msg_warn("SMTPD access map %s entry %s causes unreasonable recursion",
table, value);
longjmp(smtpd_check_buf, -1);
}
/*
* Recursively evaluate the restrictions given in the right-hand side. In
* the dark ages, an empty right-hand side meant OK. Make some
* discouraging comments.
*/
restrictions = argv_split(value, " \t\r\n,");
if (restrictions->argc == 0) {
msg_warn("SMTPD access map %s entry %s has empty value",
table, value);
status = SMTPD_CHECK_OK;
} else {
status = generic_checks(state, restrictions, reply_name,
reply_class, def_acl);
}
argv_free(restrictions);
return (status);
}
/* check_access - table lookup without substring magic */
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";
char *low_name = lowercase(mystrdup(name));
const char *value;
DICT *dict;
#define CHK_ACCESS_RETURN(x) { myfree(low_name); return(x); }
#define FULL 0
#define PARTIAL DICT_FLAG_FIXED
if (msg_verbose)
msg_info("%s: %s", myname, name);
if ((dict = dict_handle(table)) == 0)
msg_panic("%s: dictionary not found: %s", myname, table);
if (flags == 0 || (flags & dict->flags) != 0) {
if ((value = dict_get(dict, low_name)) != 0)
CHK_ACCESS_RETURN(check_table_result(state, table, value, name,
reply_name, reply_class,
def_acl));
if (dict_errno != 0)
msg_fatal("%s: table lookup problem", table);
}
CHK_ACCESS_RETURN(SMTPD_CHECK_DUNNO);
}
/* check_domain_access - domainname-based table lookup */
static int check_domain_access(SMTPD_STATE *state, char *table,
char *domain, int flags,
char *reply_name, char *reply_class,
char *def_acl)
{
char *myname = "check_domain_access";
char *low_domain = lowercase(mystrdup(domain));
char *name;
char *next;
const char *value;
DICT *dict;
if (msg_verbose)
msg_info("%s: %s", myname, domain);
/*
* Try the name and its parent domains. Including top-level domains.
*/
#define CHK_DOMAIN_RETURN(x) { myfree(low_domain); return(x); }
if ((dict = dict_handle(table)) == 0)
msg_panic("%s: dictionary not found: %s", myname, table);
for (name = low_domain; /* void */ ; name = next + 1) {
if (flags == 0 || (flags & dict->flags) != 0) {
if ((value = dict_get(dict, name)) != 0)
CHK_DOMAIN_RETURN(check_table_result(state, table, value,
domain, reply_name, reply_class,
def_acl));
if (dict_errno != 0)
msg_fatal("%s: table lookup problem", table);
}
if ((next = strchr(name, '.')) == 0)
break;
flags = PARTIAL;
}
CHK_DOMAIN_RETURN(SMTPD_CHECK_DUNNO);
}
/* check_addr_access - address-based table lookup */
static int check_addr_access(SMTPD_STATE *state, char *table,
char *address, int flags,
char *reply_name, char *reply_class,
char *def_acl)
{
char *myname = "check_addr_access";
char *addr;
const char *value;
DICT *dict;
if (msg_verbose)
msg_info("%s: %s", myname, address);
/*
* Try the address and its parent networks.
*/
addr = STR(vstring_strcpy(error_text, address));
if ((dict = dict_handle(table)) == 0)
msg_panic("%s: dictionary not found: %s", myname, table);
do {
if (flags == 0 || (flags & dict->flags) != 0) {
if ((value = dict_get(dict, addr)) != 0)
return (check_table_result(state, table, value, address,
reply_name, reply_class,
def_acl));
if (dict_errno != 0)
msg_fatal("%s: table lookup problem", table);
}
flags = PARTIAL;
} while (split_at_right(addr, '.'));
return (SMTPD_CHECK_DUNNO);
}
/* check_namadr_access - OK/FAIL based on host name/address lookup */
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)
{
char *myname = "check_namadr_access";
int status;
if (msg_verbose)
msg_info("%s: name %s addr %s", myname, name, addr);
/*
* Look up the host name, or parent domains thereof. XXX A domain
* wildcard may pre-empt a more specific address table entry.
*/
if ((status = check_domain_access(state, table, name, flags,
reply_name, reply_class,
def_acl)) != 0)
return (status);
/*
* Look up the network address, or parent networks thereof.
*/
if ((status = check_addr_access(state, table, addr, flags,
reply_name, reply_class,
def_acl)) != 0)
return (status);
/*
* Undecided when the host was not found.
*/
return (SMTPD_CHECK_DUNNO);
}
/* check_mail_access - OK/FAIL based on mail address lookup */
static int check_mail_access(SMTPD_STATE *state, char *table, char *addr,
char *reply_name, char *reply_class,
char *def_acl)
{
char *myname = "check_mail_access";
char *ratsign;
int status;
char *local_at;
if (msg_verbose)
msg_info("%s: %s", myname, addr);
/*
* Resolve the address.
*/
canon_addr_internal(query, addr);
resolve_clnt_query(STR(query), &reply);
lowercase(STR(reply.recipient));
/*
* Garbage in, garbage out. Every address from canon_addr_internal() and
* from resolve_clnt_query() must be fully qualified.
*/
if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) {
msg_warn("%s: no @domain in address: %s", myname, STR(reply.recipient));
return (0);
}
/*
* Look up the full address.
*/
if ((status = check_access(state, table, STR(reply.recipient), FULL,
reply_name, reply_class, def_acl)) != 0)
return (status);
/*
* Look up the domain name, or parent domains thereof.
*/
if ((status = check_domain_access(state, table, ratsign + 1, PARTIAL,
reply_name, reply_class, def_acl)) != 0)
return (status);
/*
* Look up localpart@
*/
local_at = mystrndup(STR(reply.recipient),
ratsign - STR(reply.recipient) + 1);
status = check_access(state, table, local_at, PARTIAL,
reply_name, reply_class, def_acl);
myfree(local_at);
if (status != 0)
return (status);
/*
* Undecided when no match found.
*/
return (SMTPD_CHECK_DUNNO);
}
/* reject_maps_rbl - reject if client address in real-time blackhole list */
static int reject_maps_rbl(SMTPD_STATE *state)
{
char *myname = "reject_maps_rbl";
ARGV *octets = argv_split(state->addr, ".");
VSTRING *query = vstring_alloc(100);
char *saved_domains = mystrdup(var_maps_rbl_domains);
char *bp = saved_domains;
char *rbl_domain;
char *rbl_reason;
char *rbl_fodder;
DNS_RR *txt_list;
int reverse_len;
int dns_status = DNS_FAIL;
int i;
int result;
if (msg_verbose)
msg_info("%s: %s", myname, state->addr);
/*
* Build the constant part of the RBL query: the reverse client address.
*/
for (i = octets->argc - 1; i >= 0; i--) {
vstring_strcat(query, octets->argv[i]);
vstring_strcat(query, ".");
}
reverse_len = VSTRING_LEN(query);
/*
* Tack on each RBL domain name and query the DNS for an A record. If the
* record exists, the client address is blacklisted.
*/
while ((rbl_domain = mystrtok(&bp, " \t\r\n,")) != 0) {
vstring_truncate(query, reverse_len);
vstring_strcat(query, rbl_domain);
dns_status = dns_lookup(STR(query), T_A, 0, (DNS_RR **) 0,
(VSTRING *) 0, (VSTRING *) 0);
if (dns_status == DNS_OK)
break;
}
/*
* Report the result.
*/
if (dns_status == DNS_OK) {
if (dns_lookup(STR(query), T_TXT, 0, &txt_list,
(VSTRING *) 0, (VSTRING *) 0) == DNS_OK) {
rbl_fodder = ", reason: ";
rbl_reason = (char *) txt_list->data;
} else {
txt_list = 0;
rbl_fodder = rbl_reason = "";
}
result = smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d Service unavailable; [%s] blocked using %s%s%s",
var_maps_rbl_code, state->addr, rbl_domain,
rbl_fodder, rbl_reason);
if (txt_list)
dns_rr_free(txt_list);
} else
result = SMTPD_CHECK_DUNNO;
/*
* Clean up.
*/
argv_free(octets);
vstring_free(query);
myfree(saved_domains);
return (result);
}
/* is_map_command - restriction has form: check_xxx_access type:name */
static int is_map_command(char *name, char *command, char ***argp)
{
/*
* This is a three-valued function: (a) this is not a check_xxx_access
* command, (b) this is a malformed check_xxx_access command, (c) this is
* a well-formed check_xxx_access command. That's too clumsy for function
* result values, so we use regular returns for (a) and (c), and use long
* jumps for the error case (b).
*/
if (strcasecmp(name, command) != 0) {
return (0);
} else if (*(*argp + 1) == 0 || strchr(*(*argp += 1), ':') == 0) {
msg_warn("restriction %s requires maptype:mapname", command);
longjmp(smtpd_check_buf, -1);
} else {
return (1);
}
}
/* generic_checks - generic restrictions */
static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
char *reply_name, char *reply_class, char *def_acl)
{
char *myname = "generic_checks";
char **cpp;
char *name;
int status = 0;
ARGV *list;
if (msg_verbose)
msg_info("%s: START", myname);
for (cpp = restrictions->argv; (name = *cpp) != 0; cpp++) {
if (msg_verbose)
msg_info("%s: name=%s", myname, name);
/*
* Spoof the is_map_command() routine, so that we do not have to make
* special cases for the implicit short-hand access map notation.
*/
if (strchr(name, ':') != 0) {
name = def_acl;
cpp -= 1;
}
/*
* Generic restrictions.
*/
if (strcasecmp(name, PERMIT_ALL) == 0) {
status = SMTPD_CHECK_OK;
if (cpp[1] != 0)
msg_warn("restriction `%s' after `%s' is ignored",
cpp[1], PERMIT_ALL);
} else if (strcasecmp(name, REJECT_ALL) == 0) {
status = smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: Access denied",
var_reject_code, reply_name, reply_class);
if (cpp[1] != 0)
msg_warn("restriction `%s' after `%s' is ignored",
cpp[1], REJECT_ALL);
} else if (strcasecmp(name, REJECT_UNAUTH_PIPE) == 0) {
status = reject_unauth_pipelining(state);
}
/*
* Client name/address restrictions.
*/
else if (strcasecmp(name, REJECT_UNKNOWN_CLIENT) == 0) {
status = reject_unknown_client(state);
} else if (strcasecmp(name, PERMIT_MYNETWORKS) == 0) {
status = permit_mynetworks(state);
} else if (is_map_command(name, CHECK_CLIENT_ACL, &cpp)) {
status = check_namadr_access(state, *cpp, state->name, state->addr,
FULL, state->namaddr,
SMTPD_NAME_CLIENT, def_acl);
} else if (strcasecmp(name, REJECT_MAPS_RBL) == 0) {
status = reject_maps_rbl(state);
}
/*
* HELO/EHLO parameter restrictions.
*/
else if (is_map_command(name, CHECK_HELO_ACL, &cpp)) {
if (state->helo_name)
status = check_domain_access(state, *cpp, state->helo_name,
FULL, state->helo_name,
SMTPD_NAME_HELO, def_acl);
} else if (strcasecmp(name, REJECT_INVALID_HOSTNAME) == 0) {
if (state->helo_name) {
if (*state->helo_name != '[')
status = reject_invalid_hostname(state, state->helo_name,
state->helo_name, SMTPD_NAME_HELO);
else
status = reject_invalid_hostaddr(state, state->helo_name,
state->helo_name, SMTPD_NAME_HELO);
}
} else if (strcasecmp(name, REJECT_UNKNOWN_HOSTNAME) == 0) {
if (state->helo_name) {
if (*state->helo_name != '[')
status = reject_unknown_hostname(state, state->helo_name,
state->helo_name, SMTPD_NAME_HELO);
else
status = reject_invalid_hostaddr(state, state->helo_name,
state->helo_name, SMTPD_NAME_HELO);
}
} else if (strcasecmp(name, PERMIT_NAKED_IP_ADDR) == 0) {
if (state->helo_name) {
if (state->helo_name[strspn(state->helo_name, "0123456789.")] == 0
&& (status = reject_invalid_hostaddr(state, state->helo_name,
state->helo_name, SMTPD_NAME_HELO)) == 0)
status = SMTPD_CHECK_OK;
}
} else if (strcasecmp(name, REJECT_NON_FQDN_HOSTNAME) == 0) {
if (state->helo_name) {
if (*state->helo_name != '[')
status = reject_non_fqdn_hostname(state, state->helo_name,
state->helo_name, SMTPD_NAME_HELO);
else
status = reject_invalid_hostaddr(state, state->helo_name,
state->helo_name, SMTPD_NAME_HELO);
}
}
/*
* Sender mail address restrictions.
*/
else if (is_map_command(name, CHECK_SENDER_ACL, &cpp)) {
if (state->sender && *state->sender)
status = check_mail_access(state, *cpp, state->sender,
state->sender,
SMTPD_NAME_SENDER, def_acl);
} else if (strcasecmp(name, REJECT_UNKNOWN_ADDRESS) == 0) {
if (state->sender && *state->sender)
status = reject_unknown_address(state, state->sender,
state->sender, SMTPD_NAME_SENDER);
} else if (strcasecmp(name, REJECT_UNKNOWN_SENDDOM) == 0) {
if (state->sender && *state->sender)
status = reject_unknown_address(state, state->sender,
state->sender, SMTPD_NAME_SENDER);
} else if (strcasecmp(name, REJECT_NON_FQDN_SENDER) == 0) {
if (state->sender && *state->sender)
status = reject_non_fqdn_address(state, state->sender,
state->sender, SMTPD_NAME_SENDER);
}
/*
* Recipient mail address restrictions.
*/
else if (is_map_command(name, CHECK_RECIP_ACL, &cpp)) {
if (state->recipient)
status = check_mail_access(state, *cpp, state->recipient,
state->recipient,
SMTPD_NAME_RECIPIENT, def_acl);
} else if (strcasecmp(name, PERMIT_MX_BACKUP) == 0) {
if (state->recipient)
status = permit_mx_backup(state, state->recipient);
} else if (strcasecmp(name, PERMIT_AUTH_DEST) == 0) {
if (state->recipient)
status = permit_auth_destination(state->recipient);
} else if (strcasecmp(name, REJECT_UNAUTH_DEST) == 0) {
if (state->recipient)
status = reject_unauth_destination(state, state->recipient);
} else if (strcasecmp(name, CHECK_RELAY_DOMAINS) == 0) {
if (state->recipient)
status = check_relay_domains(state, state->recipient,
state->recipient, SMTPD_NAME_RECIPIENT);
if (cpp[1] != 0)
msg_warn("restriction `%s' after `%s' is ignored",
cpp[1], CHECK_RELAY_DOMAINS);
} else if (strcasecmp(name, REJECT_UNKNOWN_RCPTDOM) == 0) {
if (state->recipient)
status = reject_unknown_address(state, state->recipient,
state->recipient, SMTPD_NAME_RECIPIENT);
} else if (strcasecmp(name, REJECT_NON_FQDN_RCPT) == 0) {
if (state->recipient)
status = reject_non_fqdn_address(state, state->recipient,
state->recipient, SMTPD_NAME_RECIPIENT);
}
/*
* ETRN domain name restrictions.
*/
else if (is_map_command(name, CHECK_ETRN_ACL, &cpp)) {
if (state->etrn_name)
status = check_domain_access(state, *cpp, state->etrn_name,
FULL, state->etrn_name,
SMTPD_NAME_ETRN, def_acl);
}
/*
* User-defined restriction class.
*/
else if ((list = (ARGV *) htable_find(smtpd_rest_classes, name)) != 0) {
status = generic_checks(state, list, reply_name,
reply_class, def_acl);
}
/*
* Error: undefined restriction name.
*/
else {
msg_warn("unknown smtpd restriction: \"%s\"", name);
break;
}
if (msg_verbose)
msg_info("%s: name=%s status=%d", myname, name, status);
if (status != 0)
break;
}
if (msg_verbose && name == 0)
msg_info("%s: END", myname);
return (status);
}
/* smtpd_check_client - validate client name or address */
char *smtpd_check_client(SMTPD_STATE *state)
{
int status;
/*
* Initialize.
*/
if (state->name == 0 || state->addr == 0)
return (0);
/*
* Apply restrictions in the order as specified.
*/
state->recursion = 0;
status = setjmp(smtpd_check_buf);
if (status == 0 && client_restrctions->argc)
status = generic_checks(state, client_restrctions, state->namaddr,
SMTPD_NAME_CLIENT, CHECK_CLIENT_ACL);
return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
/* smtpd_check_helo - validate HELO hostname */
char *smtpd_check_helo(SMTPD_STATE *state, char *helohost)
{
int status;
char *saved_helo;
/*
* Initialize.
*/
if (helohost == 0)
return (0);
/*
* Minor kluge so that we can delegate work to the generic routine and so
* that we can syslog the recipient with the reject messages.
*/
#define SMTPD_CHECK_PUSH(backup, current, new) { \
backup = current; \
current = mystrdup(new); \
}
#define SMTPD_CHECK_POP(current, backup) { \
myfree(current); \
current = backup; \
}
SMTPD_CHECK_PUSH(saved_helo, state->helo_name, helohost);
#define SMTPD_CHECK_HELO_RETURN(x) { \
SMTPD_CHECK_POP(state->helo_name, saved_helo); \
return (x); \
}
/*
* Apply restrictions in the order as specified.
*/
state->recursion = 0;
status = setjmp(smtpd_check_buf);
if (status == 0 && helo_restrctions->argc)
status = generic_checks(state, helo_restrctions, state->helo_name,
SMTPD_NAME_HELO, CHECK_HELO_ACL);
SMTPD_CHECK_HELO_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
/* smtpd_check_mail - validate sender address, driver */
char *smtpd_check_mail(SMTPD_STATE *state, char *sender)
{
int status;
char *saved_sender;
/*
* Initialize.
*/
if (sender == 0)
return (0);
/*
* 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_sender, state->sender, sender);
#define SMTPD_CHECK_MAIL_RETURN(x) { \
SMTPD_CHECK_POP(state->sender, saved_sender); \
return (x); \
}
/*
* Apply restrictions in the order as specified.
*/
state->recursion = 0;
status = setjmp(smtpd_check_buf);
if (status == 0 && mail_restrctions->argc)
status = generic_checks(state, mail_restrctions, sender,
SMTPD_NAME_SENDER, CHECK_SENDER_ACL);
SMTPD_CHECK_MAIL_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
/* smtpd_check_rcpt - validate recipient address, driver */
char *smtpd_check_rcpt(SMTPD_STATE *state, char *recipient)
{
int status;
char *saved_recipient;
char *err;
/*
* Initialize.
*/
if (recipient == 0)
return (0);
/*
* 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);
#define SMTPD_CHECK_RCPT_RETURN(x) { \
SMTPD_CHECK_POP(state->recipient, saved_recipient); \
return (x); \
}
/*
* Apply delayed restrictions.
*/
if (var_smtpd_delay_reject)
if ((err = smtpd_check_client(state)) != 0
|| (err = smtpd_check_helo(state, state->helo_name)) != 0
|| (err = smtpd_check_mail(state, state->sender)) != 0)
SMTPD_CHECK_RCPT_RETURN(err);
/*
* Apply restrictions in the order as specified.
*/
state->recursion = 0;
status = setjmp(smtpd_check_buf);
if (status == 0 && rcpt_restrctions->argc)
status = generic_checks(state, rcpt_restrctions,
recipient, SMTPD_NAME_RECIPIENT, CHECK_RECIP_ACL);
SMTPD_CHECK_RCPT_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
/* smtpd_check_etrn - validate ETRN request */
char *smtpd_check_etrn(SMTPD_STATE *state, char *domain)
{
int status;
char *saved_etrn_name;
char *err;
/*
* Initialize.
*/
if (domain == 0)
return (0);
/*
* 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_etrn_name, state->etrn_name, domain);
#define SMTPD_CHECK_ETRN_RETURN(x) { \
SMTPD_CHECK_POP(state->etrn_name, saved_etrn_name); \
return (x); \
}
/*
* Apply delayed restrictions.
*/
if (var_smtpd_delay_reject)
if ((err = smtpd_check_client(state)) != 0
|| (err = smtpd_check_helo(state, state->helo_name)) != 0)
SMTPD_CHECK_ETRN_RETURN(err);
/*
* Apply restrictions in the order as specified.
*/
state->recursion = 0;
status = setjmp(smtpd_check_buf);
if (status == 0 && etrn_restrctions->argc)
status = generic_checks(state, etrn_restrctions, domain,
SMTPD_NAME_ETRN, CHECK_ETRN_ACL);
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. This functionality
* will eventually become part of the trivial-rewrite resolver, including
* 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);
lowercase(STR(reply.recipient));
/*
* 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_virtual_maps
&& maps_find(virtual_maps, domain, 0)) {
msg_warn("virtual domain \"%s\" is listed in $mydestination",
domain);
msg_warn("the $local_recipient_maps feature requires that no");
msg_warn("virtual domains are listed in $mydestination");
msg_warn("be sure to specify the required \"%s whatever\"",
domain);
msg_warn("entry in the virtual map, as explained in the man");
msg_warn("page and in the FAQ entry for virtual domains");
SMTPD_CHECK_RCPT_RETURN(0);
}
dict_errno = 0;
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(relocated_maps, STR(reply.recipient), NOP)
&& !mail_addr_find(local_rcpt_maps, STR(reply.recipient), NOP)) {
(void) smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
"%d <%s>: User unknown",
dict_errno ? 450 : 550, recipient);
SMTPD_CHECK_RCPT_RETURN(STR(error_text));
}
} else {
dict_errno = 0;
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(relocated_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,
"%d <%s>: User unknown",
dict_errno ? 450 : 550, 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 *myname = "smtpd_check_size";
struct fsspace fsbuf;
/*
* Avoid overflow/underflow when comparing message size against available
* space.
*/
#define BLOCKS(x) ((x) / fsbuf.block_size)
if (var_message_limit > 0 && size > var_message_limit) {
(void) smtpd_check_reject(state, MAIL_ERROR_POLICY,
"552 Message size exceeds fixed limit");
return (STR(error_text));
}
fsspace(".", &fsbuf);
if (msg_verbose)
msg_info("%s: blocks %lu avail %lu min_free %lu size %lu",
myname,
(unsigned long) fsbuf.block_size,
(unsigned long) fsbuf.block_free,
(unsigned long) var_queue_minfree,
(unsigned long) size);
if (BLOCKS(var_queue_minfree) >= fsbuf.block_free
|| BLOCKS(size) >= fsbuf.block_free - BLOCKS(var_queue_minfree)
|| BLOCKS(size) >= fsbuf.block_free / 2) {
(void) smtpd_check_reject(state, MAIL_ERROR_RESOURCE,
"452 Insufficient system storage");
return (STR(error_text));
}
return (0);
}
#ifdef TEST
/*
* Test program to try out all these restrictions without having to go live.
* This is not entirely stand-alone, as it requires access to the Postfix
* rewrite/resolve service. This is just for testing code, not for debugging
* configuration files.
*/
#include <stdlib.h>
#include <msg_vstream.h>
#include <vstring_vstream.h>
#include <mail_conf.h>
#include <smtpd_chat.h>
/*
* Dummies. These are never set.
*/
char *var_client_checks = "";
char *var_helo_checks = "";
char *var_mail_checks = "";
char *var_rcpt_checks = "";
char *var_etrn_checks = "";
char *var_relay_domains = "";
char *var_mynetworks = "";
char *var_notify_classes = "";
/*
* String-valued configuration parameters.
*/
char *var_maps_rbl_domains;
char *var_mydest;
char *var_inet_interfaces;
char *var_rest_classes;
char *var_alias_maps;
char *var_rcpt_canon_maps;
char *var_canonical_maps;
char *var_virtual_maps;
char *var_relocated_maps;
char *var_local_rcpt_maps;
typedef struct {
char *name;
char *defval;
char **target;
} STRING_TABLE;
static STRING_TABLE string_table[] = {
VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains,
VAR_MYDEST, DEF_MYDEST, &var_mydest,
VAR_INET_INTERFACES, DEF_INET_INTERFACES, &var_inet_interfaces,
VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes,
VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps,
VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps,
VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps,
VAR_VIRTUAL_MAPS, DEF_VIRTUAL_MAPS, &var_virtual_maps,
VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps,
VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps,
0,
};
/* string_init - initialize string parameters */
static void string_init(void)
{
STRING_TABLE *sp;
for (sp = string_table; sp->name; sp++)
sp->target[0] = mystrdup(sp->defval);
}
/* string_update - update string parameter */
static int string_update(char **argv)
{
STRING_TABLE *sp;
for (sp = string_table; sp->name; sp++) {
if (strcasecmp(argv[0], sp->name) == 0) {
myfree(sp->target[0]);
sp->target[0] = mystrdup(argv[1]);
return (1);
}
}
return (0);
}
/*
* Integer parameters.
*/
int var_queue_minfree; /* XXX use off_t */
typedef struct {
char *name;
int defval;
int *target;
} INT_TABLE;
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;
int var_reject_code;
int var_non_fqdn_code;
int var_smtpd_delay_reject;
int var_allow_untrust_route;
static INT_TABLE int_table[] = {
"msg_verbose", 0, &msg_verbose,
VAR_UNK_CLIENT_CODE, DEF_UNK_CLIENT_CODE, &var_unk_client_code,
VAR_BAD_NAME_CODE, DEF_BAD_NAME_CODE, &var_bad_name_code,
VAR_UNK_NAME_CODE, DEF_UNK_NAME_CODE, &var_unk_name_code,
VAR_UNK_ADDR_CODE, DEF_UNK_ADDR_CODE, &var_unk_addr_code,
VAR_RELAY_CODE, DEF_RELAY_CODE, &var_relay_code,
VAR_MAPS_RBL_CODE, DEF_MAPS_RBL_CODE, &var_maps_rbl_code,
VAR_ACCESS_MAP_CODE, DEF_ACCESS_MAP_CODE, &var_access_map_code,
VAR_REJECT_CODE, DEF_REJECT_CODE, &var_reject_code,
VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code,
VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject,
VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route,
0,
};
/* int_init - initialize int parameters */
static void int_init(void)
{
INT_TABLE *sp;
for (sp = int_table; sp->name; sp++)
sp->target[0] = sp->defval;
}
/* int_update - update int parameter */
static int int_update(char **argv)
{
INT_TABLE *ip;
for (ip = int_table; ip->name; ip++) {
if (strcasecmp(argv[0], ip->name) == 0) {
if (!ISDIGIT(*argv[1]))
msg_fatal("bad number: %s %s", ip->name, argv[1]);
ip->target[0] = atoi(argv[1]);
return (1);
}
}
return (0);
}
/*
* Restrictions.
*/
typedef struct {
char *name;
ARGV **target;
} REST_TABLE;
static REST_TABLE rest_table[] = {
"client_restrictions", &client_restrctions,
"helo_restrictions", &helo_restrctions,
"sender_restrictions", &mail_restrctions,
"recipient_restrictions", &rcpt_restrctions,
"etrn_restrictions", &etrn_restrctions,
0,
};
/* rest_update - update restriction */
static int rest_update(char **argv)
{
REST_TABLE *rp;
for (rp = rest_table; rp->name; rp++) {
if (strcasecmp(rp->name, argv[0]) == 0) {
argv_free(rp->target[0]);
rp->target[0] = smtpd_check_parse(argv[1]);
return (1);
}
}
return (0);
}
/* rest_class - (re)define a restriction class */
static void rest_class(char *class)
{
char *cp = class;
char *name;
HTABLE_INFO *entry;
if (smtpd_rest_classes == 0)
smtpd_rest_classes = htable_create(1);
if ((name = mystrtok(&cp, " \t\r\n,")) == 0)
msg_panic("rest_class: null class name");
if ((entry = htable_locate(smtpd_rest_classes, name)) != 0)
argv_free((ARGV *) entry->value);
else
entry = htable_enter(smtpd_rest_classes, name, (char *) 0);
entry->value = (char *) smtpd_check_parse(cp);
}
/* resolve_clnt_init - initialize reply */
void resolve_clnt_init(RESOLVE_REPLY *reply)
{
reply->transport = vstring_alloc(100);
reply->nexthop = vstring_alloc(100);
reply->recipient = vstring_alloc(100);
}
/* canon_addr_internal - stub */
VSTRING *canon_addr_internal(VSTRING *result, const char *addr)
{
if (addr == STR(result))
msg_panic("canon_addr_internal: result clobbers input");
if (strchr(addr, '@') == 0)
msg_fatal("%s: address rewriting is disabled", addr);
vstring_strcpy(result, addr);
}
/* resolve_clnt_query - stub */
void resolve_clnt_query(const char *addr, RESOLVE_REPLY *reply)
{
if (addr == STR(reply->recipient))
msg_panic("resolve_clnt_query: result clobbers input");
vstring_strcpy(reply->transport, "foo");
vstring_strcpy(reply->nexthop, "foo");
if (strchr(addr, '%'))
msg_fatal("%s: address rewriting is disabled", addr);
vstring_strcpy(reply->recipient, addr);
}
/* smtpd_chat_reset - stub */
void smtpd_chat_reset(SMTPD_STATE *unused_state)
{
}
/* usage - scream and terminate */
static NORETURN usage(char *myname)
{
msg_fatal("usage: %s", myname);
}
main(int argc, char **argv)
{
VSTRING *buf = vstring_alloc(100);
SMTPD_STATE state;
ARGV *args;
char *bp;
char *resp;
/*
* Initialization. Use dummies for client information.
*/
msg_vstream_init(argv[0], VSTREAM_ERR);
if (argc != 1)
usage(argv[0]);
string_init();
int_init();
smtpd_check_init();
smtpd_state_init(&state, VSTREAM_IN);
state.queue_id = "<queue id>";
/*
* Main loop: update config parameters or test the client, helo, sender
* and recipient restrictions.
*/
while (vstring_fgets_nonl(buf, VSTREAM_IN) != 0) {
/*
* Tokenize the command. Note, the comma is not a separator, so that
* restriction lists can be entered as comma-separated lists.
*/
bp = STR(buf);
if (!isatty(0)) {
vstream_printf(">>> %s\n", bp);
vstream_fflush(VSTREAM_OUT);
}
if (*bp == '#')
continue;
if (*bp == '!') {
vstream_printf("exit %d\n", system(bp + 1));
continue;
}
args = argv_split(bp, " \t\r\n");
/*
* Recognize the command.
*/
resp = "bad command";
switch (args->argc) {
/*
* Special case: client identity.
*/
case 4:
case 3:
#define UPDATE_STRING(ptr,val) { if (ptr) myfree(ptr); ptr = mystrdup(val); }
if (strcasecmp(args->argv[0], "client") == 0) {
state.where = "CONNECT";
UPDATE_STRING(state.name, args->argv[1]);
UPDATE_STRING(state.addr, args->argv[2]);
if (args->argc == 4)
state.peer_code = atoi(args->argv[3]);
else
state.peer_code = 2;
if (state.namaddr)
myfree(state.namaddr);
state.namaddr = concatenate(state.name, "[", state.addr,
"]", (char *) 0);
resp = smtpd_check_client(&state);
}
break;
/*
* Try config settings.
*/
case 2:
if (strcasecmp(args->argv[0], "mynetworks") == 0) {
namadr_list_free(mynetworks);
mynetworks = namadr_list_init(args->argv[1]);
resp = 0;
break;
}
if (strcasecmp(args->argv[0], "relay_domains") == 0) {
domain_list_free(relay_domains);
relay_domains = domain_list_init(args->argv[1]);
resp = 0;
break;
}
if (strcasecmp(args->argv[0], "restriction_class") == 0) {
rest_class(args->argv[1]);
resp = 0;
break;
}
if (int_update(args->argv)
|| string_update(args->argv)
|| rest_update(args->argv)) {
resp = 0;
break;
}
/*
* Try restrictions.
*/
if (strcasecmp(args->argv[0], "helo") == 0) {
state.where = "HELO";
resp = smtpd_check_helo(&state, args->argv[1]);
UPDATE_STRING(state.helo_name, args->argv[1]);
} else if (strcasecmp(args->argv[0], "mail") == 0) {
state.where = "MAIL";
resp = smtpd_check_mail(&state, args->argv[1]);
UPDATE_STRING(state.sender, args->argv[1]);
} else if (strcasecmp(args->argv[0], "rcpt") == 0) {
state.where = "RCPT";
resp = smtpd_check_rcpt(&state, args->argv[1]);
}
break;
/*
* Show commands.
*/
default:
resp = "Commands...\n\
client <name> <address> [<code>]\n\
helo <hostname>\n\
sender <address>\n\
recipient <address>\n\
msg_verbose <level>\n\
client_restrictions <restrictions>\n\
helo_restrictions <restrictions>\n\
sender_restrictions <restrictions>\n\
recipient_restrictions <restrictions>\n\
restriction_class name,<restrictions>\n\
\n\
Note: no address rewriting \n";
break;
}
vstream_printf("%s\n", resp ? resp : "OK");
vstream_fflush(VSTREAM_OUT);
argv_free(args);
}
vstring_free(buf);
smtpd_state_reset(&state);
#define FREE_STRING(s) { if (s) myfree(s); }
FREE_STRING(state.helo_name);
FREE_STRING(state.sender);
exit(0);
}
#endif