#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
#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>
#include <ctable.h>
#include <mac_expand.h>
#include <dns.h>
#include <string_list.h>
#include <namadr_list.h>
#include <domain_list.h>
#include <string_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>
#include <match_parent_style.h>
#include <strip_addr.h>
#include <virtual8_maps.h>
#include <cleanup_user.h>
#include <record.h>
#include <rec_type.h>
#include <mail_proto.h>
#include <mail_addr.h>
#include "smtpd.h"
#include "smtpd_sasl_glue.h"
#include "smtpd_check.h"
static jmp_buf smtpd_check_buf;
#define SMTPD_CHECK_DUNNO 0
#define SMTPD_CHECK_OK 1
#define SMTPD_CHECK_REJECT 2
static VSTRING *error_text;
static CTABLE *smtpd_resolve_cache;
static CTABLE *smtpd_rbl_cache;
static MAPS *local_rcpt_maps;
static MAPS *rcpt_canon_maps;
static MAPS *canonical_maps;
static MAPS *virt_alias_maps;
static MAPS *virt_mailbox_maps;
static MAPS *relay_rcpt_maps;
#ifdef TEST
static STRING_LIST *virt_alias_doms;
static STRING_LIST *virt_mailbox_doms;
#endif
static MAPS *rbl_reply_maps;
static MAPS *smtpd_sender_login_maps;
static DOMAIN_LIST *relay_domains;
static NAMADR_LIST *mynetworks;
static NAMADR_LIST *perm_mx_networks;
#ifdef HAS_SSL
static MAPS *relay_ccerts;
#endif
static int access_parent_style;
static ARGV *client_restrctions;
static ARGV *helo_restrctions;
static ARGV *mail_restrctions;
static ARGV *rcpt_restrctions;
static ARGV *etrn_restrctions;
static ARGV *data_restrctions;
static HTABLE *smtpd_rest_classes;
static VSTRING *expand_filter;
static int generic_checks(SMTPD_STATE *, ARGV *, const char *, const char *, const char *);
static int check_rcpt_maps(SMTPD_STATE *state, const char *recipient);
#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"
#define SMTPD_NAME_DATA "Data command"
#define STR vstring_str
#define CONST_STR(x) ((const char *) vstring_str(x))
static void PRINTFLIKE(3, 4) defer_if(SMTPD_DEFER *, int, const char *,...);
#define DEFER_IF_REJECT2(state, class, fmt, a1, a2) \
defer_if(&(state)->defer_if_reject, (class), (fmt), (a1), (a2))
#define DEFER_IF_REJECT3(state, class, fmt, a1, a2, a3) \
defer_if(&(state)->defer_if_reject, (class), (fmt), (a1), (a2), (a3))
#define DEFER_IF_PERMIT2(state, class, fmt, a1, a2) do { \
if ((state)->warn_if_reject == 0) \
defer_if(&(state)->defer_if_permit, (class), (fmt), (a1), (a2)); \
else \
(void) smtpd_check_reject((state), (class), (fmt), (a1), (a2)); \
} while (0)
typedef struct {
char *txt;
} SMTPD_RBL_STATE;
static void *rbl_pagein(const char *, void *);
static void rbl_pageout(void *, void *);
typedef struct {
SMTPD_STATE *state;
SMTPD_RBL_STATE *rbl_state;
const char *domain;
const char *what;
const char *class;
} SMTPD_RBL_EXPAND_CONTEXT;
static void *resolve_pagein(const char *addr, void *unused_context)
{
static VSTRING *query;
RESOLVE_REPLY *reply;
if (query == 0)
query = vstring_alloc(10);
reply = (RESOLVE_REPLY *) mymalloc(sizeof(*reply));
resolve_clnt_init(reply);
canon_addr_internal(query, addr);
resolve_clnt_query(STR(query), reply);
lowercase(STR(reply->recipient));
return ((void *) reply);
}
static void resolve_pageout(void *data, void *unused_context)
{
RESOLVE_REPLY *reply = (RESOLVE_REPLY *) data;
resolve_clnt_free(reply);
myfree((void *) reply);
}
static ARGV *smtpd_check_parse(const char *checks)
{
char *saved_checks = mystrdup(checks);
ARGV *argv = argv_alloc(1);
char *bp = saved_checks;
char *name;
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);
myfree(saved_checks);
return (argv);
}
static int has_required(ARGV *restrictions, char **required)
{
char **rest;
char **reqd;
ARGV *expansion;
for (rest = restrictions->argv; *rest; rest++) {
if (strcmp(*rest, WARN_IF_REJECT) == 0 && rest[1] != 0) {
rest += 1;
continue;
}
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);
}
static void fail_required(char *name, char **required)
{
char *myname = "fail_required";
char **reqd;
VSTRING *example;
if (required[0] == 0)
msg_panic("%s: null required list", myname);
example = vstring_alloc(10);
for (reqd = required; *reqd; reqd++)
vstring_sprintf_append(example, "%s%s", *reqd,
reqd[1] == 0 ? "" : reqd[2] == 0 ? " or " : ", ");
msg_fatal("parameter \"%s\": specify at least one working instance of: %s",
name, STR(example));
}
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,
DEFER_ALL,
DEFER_IF_PERMIT,
0,
};
mynetworks =
namadr_list_init(match_parent_style(VAR_MYNETWORKS),
var_mynetworks);
relay_domains =
domain_list_init(match_parent_style(VAR_RELAY_DOMAINS),
var_relay_domains);
perm_mx_networks =
namadr_list_init(match_parent_style(VAR_PERM_MX_NETWORKS),
var_perm_mx_networks);
#ifdef HAS_SSL
relay_ccerts = maps_create(VAR_RELAY_CCERTS, var_relay_ccerts,
DICT_FLAG_LOCK);
#endif
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);
virt_alias_maps = maps_create(VAR_VIRT_ALIAS_MAPS, var_virt_alias_maps,
DICT_FLAG_LOCK);
virt_mailbox_maps = virtual8_maps_create(VAR_VIRT_MAILBOX_MAPS,
var_virt_mailbox_maps,
DICT_FLAG_LOCK);
relay_rcpt_maps = maps_create(VAR_RELAY_RCPT_MAPS, var_relay_rcpt_maps,
DICT_FLAG_LOCK);
#ifdef TEST
virt_alias_doms = string_list_init(MATCH_FLAG_NONE, var_virt_alias_doms);
virt_mailbox_doms = string_list_init(MATCH_FLAG_NONE, var_virt_mailbox_doms);
#endif
access_parent_style = match_parent_style(SMTPD_ACCESS_MAPS);
rbl_reply_maps = maps_create(VAR_RBL_REPLY_MAPS, var_rbl_reply_maps,
DICT_FLAG_LOCK);
smtpd_sender_login_maps = maps_create(VAR_SMTPD_SND_AUTH_MAPS,
var_smtpd_snd_auth_maps,
DICT_FLAG_LOCK);
error_text = vstring_alloc(10);
smtpd_resolve_cache = ctable_create(100, resolve_pagein,
resolve_pageout, (void *) 0);
smtpd_rbl_cache = ctable_create(100, rbl_pagein, rbl_pageout, (void *) 0);
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);
data_restrctions = smtpd_check_parse(var_data_checks);
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);
}
#if 0
htable_enter(smtpd_rest_classes, "check_relay_domains",
smtpd_check_parse("permit_mydomain reject_unauth_destination"));
#endif
#ifndef TEST
if (!has_required(rcpt_restrctions, rcpt_required))
fail_required(VAR_RCPT_CHECKS, rcpt_required);
#endif
expand_filter = vstring_alloc(10);
unescape(expand_filter, var_smtpd_exp_filter);
}
static void log_whatsup(SMTPD_STATE *state, const char *whatsup,
const char *text)
{
VSTRING *buf = vstring_alloc(100);
vstring_sprintf(buf, "%s: %s: %s from %s: %s;",
state->queue_id ? state->queue_id : "NOQUEUE",
whatsup, state->where, state->namaddr, text);
if (state->sender)
vstring_sprintf_append(buf, " from=<%s>", state->sender);
if (state->recipient)
vstring_sprintf_append(buf, " to=<%s>", state->recipient);
if (state->protocol)
vstring_sprintf_append(buf, " proto=%s", state->protocol);
if (state->helo_name)
vstring_sprintf_append(buf, " helo=<%s>", state->helo_name);
msg_info("%s", STR(buf));
vstring_free(buf);
}
static int smtpd_check_reject(SMTPD_STATE *state, int error_class,
char *format,...)
{
va_list ap;
int warn_if_reject;
const char *whatsup;
if (state->warn_if_reject && error_class != MAIL_ERROR_SOFTWARE) {
warn_if_reject = 1;
whatsup = "reject_warning";
} else {
warn_if_reject = 0;
whatsup = "reject";
}
state->error_mask |= error_class;
va_start(ap, format);
vstring_vsprintf(error_text, format, ap);
va_end(ap);
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), ' ');
if (!warn_if_reject && state->defer_if_reject.active && STR(error_text)[0] == '5') {
state->warn_if_reject = state->defer_if_reject.active = 0;
return (smtpd_check_reject(state, state->defer_if_reject.class,
"%s", STR(state->defer_if_reject.reason)));
}
if (var_soft_bounce && STR(error_text)[0] == '5')
STR(error_text)[0] = '4';
log_whatsup(state, whatsup, STR(error_text));
return (warn_if_reject ? 0 : SMTPD_CHECK_REJECT);
}
static void defer_if(SMTPD_DEFER *defer, int error_class, const char *fmt,...)
{
va_list ap;
if (defer->active == 0) {
defer->active = 1;
defer->class = error_class;
if (defer->reason == 0)
defer->reason = vstring_alloc(10);
va_start(ap, fmt);
vstring_vsprintf(defer->reason, fmt, ap);
va_end(ap);
}
}
static void reject_dict_retry(SMTPD_STATE *state, const char *reply_name)
{
longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_RESOURCE,
"%d <%s>: Temporary lookup failure",
451, reply_name));
}
static const char *checkv8_maps_find(SMTPD_STATE *state, const char *reply_name,
MAPS *maps, const char *key)
{
const char *result;
dict_errno = 0;
if ((result = virtual8_maps_find(maps, key)) == 0
&& dict_errno == DICT_ERR_RETRY)
reject_dict_retry(state, reply_name);
return (result);
}
static const char *check_mail_addr_find(SMTPD_STATE *state,
const char *reply_name,
MAPS *maps, const char *key,
char **ext)
{
const char *result;
dict_errno = 0;
if ((result = mail_addr_find(maps, key, ext)) == 0
&& dict_errno == DICT_ERR_RETRY)
reject_dict_retry(state, reply_name);
return (result);
}
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);
}
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);
}
static char *dup_if_truncate(char *name)
{
int len;
char *result;
if ((len = strlen(name)) > 1
&& name[len - 1] == '.'
&& name[len - 2] != '.') {
result = mystrndup(name, len - 1);
} else
result = name;
return (result);
}
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;
if (!valid_hostaddr(test_addr, DONT_GRIPE))
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;
if (test_addr != addr)
myfree(test_addr);
return (stat);
}
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);
test_name = dup_if_truncate(name);
if (!valid_hostname(test_name, DONT_GRIPE))
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;
if (test_name != name)
myfree(test_name);
return (stat);
}
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);
test_name = dup_if_truncate(name);
if (!valid_hostname(test_name, DONT_GRIPE) || !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;
if (test_name != name)
myfree(test_name);
return (stat);
}
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);
#ifdef T_AAAA
#define RR_ADDR_TYPES T_A, T_AAAA
#else
#define RR_ADDR_TYPES T_A
#endif
dns_status = dns_lookup_types(name, 0, (DNS_RR **) 0, (VSTRING *) 0,
(VSTRING *) 0, RR_ADDR_TYPES, T_MX, 0);
if (dns_status == DNS_NOTFOUND)
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: Host not found",
var_unk_name_code,
reply_name, reply_class));
else if (dns_status != DNS_OK)
DEFER_IF_PERMIT2(state, MAIL_ERROR_POLICY,
"450 <%s>: %s rejected: Host not found",
reply_name, reply_class);
return (SMTPD_CHECK_DUNNO);
}
static int reject_unknown_mailhost(SMTPD_STATE *state, const char *name,
const char *reply_name, const 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, RR_ADDR_TYPES, T_MX, 0);
if (dns_status == DNS_NOTFOUND)
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: Domain not found",
var_unk_addr_code,
reply_name, reply_class));
else if (dns_status != DNS_OK)
DEFER_IF_PERMIT2(state, MAIL_ERROR_POLICY,
"450 <%s>: %s rejected: Domain not found",
reply_name, reply_class);
return (SMTPD_CHECK_DUNNO);
}
static int permit_auth_destination(SMTPD_STATE *state, char *recipient);
#ifdef HAS_SSL
static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs)
{
char *low_name;
const char *found;
if (state->tls_info.peer_verified && permit_all_certs) {
if (msg_verbose)
msg_info("Relaying allowed for all verified client certificates");
return(SMTPD_CHECK_OK);
}
if (state->tls_info.peer_verified && state->tls_info.peer_fingerprint) {
low_name = lowercase(mystrdup(state->tls_info.peer_fingerprint));
found = maps_find(relay_ccerts, low_name, DICT_FLAG_FIXED);
myfree(low_name);
if (found) {
if (msg_verbose)
msg_info("Relaying allowed for certified client: %s", found);
return (SMTPD_CHECK_OK);
} else if (msg_verbose)
msg_info("relay_clientcerts: No match for fingerprint '%s'",
state->tls_info.peer_fingerprint);
}
return (SMTPD_CHECK_DUNNO);
}
#endif
static int check_relay_domains(SMTPD_STATE *state, char *recipient,
char *reply_name, char *reply_class)
{
char *myname = "check_relay_domains";
#if 1
static int once;
if (once == 0) {
once = 1;
msg_warn("the \"%s\" restriction is going away; use \"%s\" instead",
CHECK_RELAY_DOMAINS, REJECT_UNAUTH_DEST);
}
#endif
if (msg_verbose)
msg_info("%s: %s", myname, recipient);
if (domain_list_match(relay_domains, state->name))
return (SMTPD_CHECK_OK);
if (permit_auth_destination(state, recipient) == SMTPD_CHECK_OK)
return (SMTPD_CHECK_OK);
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: Relay access denied",
var_relay_code, reply_name, reply_class));
}
static int permit_auth_destination(SMTPD_STATE *state, char *recipient)
{
char *myname = "permit_auth_destination";
const RESOLVE_REPLY *reply;
if (msg_verbose)
msg_info("%s: %s", myname, recipient);
reply = (const RESOLVE_REPLY *)
ctable_locate(smtpd_resolve_cache, recipient);
if (reply->flags & RESOLVE_FLAG_FAIL)
reject_dict_retry(state, recipient);
if (strrchr(CONST_STR(reply->recipient), '@') == 0)
return (SMTPD_CHECK_OK);
if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED))
return (SMTPD_CHECK_DUNNO);
if (reply->flags & RESOLVE_CLASS_FINAL)
return (SMTPD_CHECK_OK);
if (reply->flags & RESOLVE_CLASS_RELAY)
return (SMTPD_CHECK_OK);
return (SMTPD_CHECK_DUNNO);
}
static int reject_unauth_destination(SMTPD_STATE *state, char *recipient)
{
char *myname = "reject_unauth_destination";
if (msg_verbose)
msg_info("%s: %s", myname, recipient);
if (permit_auth_destination(state, recipient) == SMTPD_CHECK_OK)
return (SMTPD_CHECK_DUNNO);
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: Relay access denied",
var_relay_code, recipient));
}
static int reject_unauth_pipelining(SMTPD_STATE *state,
const char *reply_name, const char *reply_class)
{
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
|| strcasecmp(state->where, "DATA") == 0)) {
return (smtpd_check_reject(state, MAIL_ERROR_PROTOCOL,
"503 <%s>: %s rejected: Improper use of SMTP command pipelining",
reply_name, reply_class));
}
return (SMTPD_CHECK_DUNNO);
}
static int all_auth_mx_addr(SMTPD_STATE *state, char *host,
const char *reply_name, const char *reply_class)
{
char *myname = "all_auth_mx_addr";
struct in_addr addr;
DNS_RR *rr;
DNS_RR *addr_list;
int dns_status;
if (msg_verbose)
msg_info("%s: host %s", myname, host);
#define NOPE 0
#define YUP 1
dns_status = dns_lookup(host, T_A, 0, &addr_list, (VSTRING *) 0, (VSTRING *) 0);
if (dns_status != DNS_OK) {
DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
"450 <%s>: %s rejected: Unable to look up host %s as mail exchanger",
reply_name, reply_class, host);
return (NOPE);
}
for (rr = addr_list; rr != 0; rr = rr->next) {
if (rr->data_len > sizeof(addr)) {
msg_warn("%s: skipping address length %d for host %s",
state->queue_id, rr->data_len, host);
continue;
}
memcpy((char *) &addr, rr->data, sizeof(addr));
if (msg_verbose)
msg_info("%s: checking: %s", myname, inet_ntoa(addr));
if (!namadr_list_match(perm_mx_networks, host, inet_ntoa(addr))) {
if (msg_verbose)
msg_info("%s: address %s for %s does not match %s",
myname, inet_ntoa(addr), host, VAR_PERM_MX_NETWORKS);
dns_rr_free(addr_list);
return (NOPE);
}
}
dns_rr_free(addr_list);
return (YUP);
}
static int has_my_addr(SMTPD_STATE *state, const char *host,
const char *reply_name, const char *reply_class)
{
char *myname = "has_my_addr";
struct in_addr addr;
char **cpp;
struct hostent *hp;
if (msg_verbose)
msg_info("%s: host %s", myname, host);
#define YUP 1
#define NOPE 0
if ((hp = gethostbyname(host)) == 0) {
DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY,
"450 <%s>: %s rejected: Unable to look up mail exchanger host %s",
reply_name, reply_class, host);
return (NOPE);
}
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 (NOPE);
}
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 (proxy_inet_addr(&addr))
return (YUP);
}
if (msg_verbose)
msg_info("%s: host %s: no match", myname, host);
return (NOPE);
}
static int i_am_mx(SMTPD_STATE *state, DNS_RR *mx_list,
const char *reply_name, const char *reply_class)
{
const char *myname = "permit_mx_backup";
DNS_RR *mx;
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))
return (YUP);
}
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(state, (char *) mx->data, reply_name, reply_class))
return (YUP);
}
if (msg_verbose)
msg_info("%s: I am not listed as MX relay", myname);
return (NOPE);
}
static int permit_mx_primary(SMTPD_STATE *state, DNS_RR *mx_list,
const char *reply_name, const char *reply_class)
{
DNS_RR *mx;
unsigned int best_pref;
for (best_pref = 0xffff, mx = mx_list; mx != 0; mx = mx->next)
if (mx->pref < best_pref)
best_pref = mx->pref;
for (mx = mx_list; mx != 0; mx = mx->next) {
if (mx->pref != best_pref)
continue;
if (!all_auth_mx_addr(state, (char *) mx->data, reply_name, reply_class))
return (NOPE);
}
return (YUP);
}
static int permit_mx_backup(SMTPD_STATE *state, const char *recipient,
const char *reply_name, const char *reply_class)
{
char *myname = "permit_mx_backup";
const RESOLVE_REPLY *reply;
const char *domain;
DNS_RR *mx_list;
int dns_status;
if (msg_verbose)
msg_info("%s: %s", myname, recipient);
reply = (const RESOLVE_REPLY *)
ctable_locate(smtpd_resolve_cache, recipient);
if (reply->flags & RESOLVE_FLAG_FAIL)
reject_dict_retry(state, recipient);
if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0)
return (SMTPD_CHECK_OK);
domain += 1;
if (reply->flags & RESOLVE_CLASS_LOCAL)
return (SMTPD_CHECK_OK);
if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED))
return (SMTPD_CHECK_DUNNO);
if (reply->flags & RESOLVE_CLASS_FINAL)
return (SMTPD_CHECK_OK);
if (msg_verbose)
msg_info("%s: not local: %s", myname, recipient);
if (domain[0] == '[' && domain[strlen(domain) - 1] == ']')
return (SMTPD_CHECK_DUNNO);
dns_status = dns_lookup(domain, T_MX, 0, &mx_list,
(VSTRING *) 0, (VSTRING *) 0);
if (dns_status == DNS_NOTFOUND)
return (has_my_addr(state, domain, reply_name, reply_class) ?
SMTPD_CHECK_OK : SMTPD_CHECK_DUNNO);
if (dns_status != DNS_OK) {
DEFER_IF_REJECT2(state, MAIL_ERROR_POLICY,
"450 <%s>: %s rejected: Unable to look up mail exchanger information",
reply_name, reply_class);
return (SMTPD_CHECK_DUNNO);
}
if (!i_am_mx(state, mx_list, reply_name, reply_class)) {
dns_rr_free(mx_list);
return (SMTPD_CHECK_DUNNO);
}
if (*var_perm_mx_networks
&& !permit_mx_primary(state, mx_list, reply_name, reply_class)) {
dns_rr_free(mx_list);
return (SMTPD_CHECK_DUNNO);
}
dns_rr_free(mx_list);
return (SMTPD_CHECK_OK);
}
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);
if ((domain = strrchr(addr, '@')) != 0)
domain++;
else
domain = "";
if (domain[0] == '[' && domain[strlen(domain) - 1] == ']')
return (SMTPD_CHECK_DUNNO);
test_dom = dup_if_truncate(domain);
if (!*test_dom || !valid_hostname(test_dom, DONT_GRIPE) || !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;
if (test_dom != domain)
myfree(test_dom);
return (stat);
}
static int reject_unknown_address(SMTPD_STATE *state, const char *addr,
const char *reply_name, const char *reply_class)
{
char *myname = "reject_unknown_address";
const RESOLVE_REPLY *reply;
const char *domain;
if (msg_verbose)
msg_info("%s: %s", myname, addr);
reply = (const RESOLVE_REPLY *) ctable_locate(smtpd_resolve_cache, addr);
if (reply->flags & RESOLVE_FLAG_FAIL)
reject_dict_retry(state, addr);
if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0)
return (SMTPD_CHECK_DUNNO);
domain += 1;
if (reply->flags & RESOLVE_CLASS_FINAL)
return (SMTPD_CHECK_DUNNO);
if (domain[0] == '[' && domain[strlen(domain) - 1] == ']')
return (SMTPD_CHECK_DUNNO);
return (reject_unknown_mailhost(state, domain, reply_name, reply_class));
}
static void warn_skip_access_action(const char *table, const char *action,
const char *reply_class)
{
if (strcmp(reply_class, SMTPD_NAME_CLIENT) == 0
|| strcmp(reply_class, SMTPD_NAME_HELO) == 0
|| strcmp(reply_class, SMTPD_NAME_SENDER) == 0) {
if (var_smtpd_delay_reject == 0)
msg_warn("access table %s: with %s=%s, "
"action %s is always skipped in %s restrictions",
table, VAR_SMTPD_DELAY_REJECT, CONFIG_BOOL_NO,
action, reply_class);
} else {
msg_warn("access table %s: action %s is always "
"skipped in %s restrictions",
table, action, reply_class);
}
}
static int check_table_result(SMTPD_STATE *state, const char *table,
const char *value, const char *datum,
const char *reply_name,
const char *reply_class,
const char *def_acl)
{
char *myname = "check_table_result";
int code;
ARGV *restrictions;
jmp_buf savebuf;
int status;
const char *cmd_text;
int cmd_len;
cmd_text = value + strcspn(value, " \t");
cmd_len = cmd_text - value;
while (*cmd_text && ISSPACE(*cmd_text))
cmd_text++;
if (msg_verbose)
msg_info("%s: %s %s %s", myname, table, value, datum);
#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0)
if (STREQUAL(value, "DUNNO", cmd_len))
return (SMTPD_CHECK_DUNNO);
if (STREQUAL(value, "REJECT", cmd_len)) {
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: %s",
var_access_map_code, reply_name, reply_class,
*cmd_text ? cmd_text : "Access denied"));
}
if (STREQUAL(value, "FILTER", cmd_len)) {
#ifndef TEST
if (state->dest == 0) {
warn_skip_access_action(table, "FILTER", reply_class);
return (SMTPD_CHECK_DUNNO);
}
#endif
if (*cmd_text == 0) {
msg_warn("access map %s entry \"%s\" has FILTER entry without value",
table, datum);
return (SMTPD_CHECK_DUNNO);
} else if (strchr(cmd_text, ':') == 0) {
msg_warn("access map %s entry \"%s\" requires transport:destination",
table, datum);
return (SMTPD_CHECK_DUNNO);
} else {
vstring_sprintf(error_text, "<%s>: %s triggers FILTER %s",
reply_name, reply_class, cmd_text);
log_whatsup(state, "filter", STR(error_text));
#ifndef TEST
rec_fprintf(state->dest->stream, REC_TYPE_FILT, "%s", cmd_text);
#endif
return (SMTPD_CHECK_DUNNO);
}
}
if (STREQUAL(value, "HOLD", cmd_len)) {
#ifndef TEST
if (state->dest == 0) {
warn_skip_access_action(table, "HOLD", reply_class);
return (SMTPD_CHECK_DUNNO);
}
#endif
vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class,
*cmd_text ? cmd_text : "triggers HOLD action");
log_whatsup(state, "hold", STR(error_text));
#ifndef TEST
rec_fprintf(state->dest->stream, REC_TYPE_FLGS, "%d",
CLEANUP_FLAG_HOLD);
#endif
return (SMTPD_CHECK_DUNNO);
}
if (STREQUAL(value, "DISCARD", cmd_len)) {
#ifndef TEST
if (state->dest == 0) {
warn_skip_access_action(table, "DISCARD", reply_class);
return (SMTPD_CHECK_DUNNO);
}
#endif
vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class,
*cmd_text ? cmd_text : "triggers DISCARD action");
log_whatsup(state, "discard", STR(error_text));
#ifndef TEST
rec_fprintf(state->dest->stream, REC_TYPE_FLGS, "%d",
CLEANUP_FLAG_DISCARD);
state->discard = 1;
#endif
return (SMTPD_CHECK_OK);
}
if (alldig(value))
return (SMTPD_CHECK_OK);
if (cmd_len == 3 && *cmd_text
&& ISDIGIT(value[0]) && ISDIGIT(value[1]) && ISDIGIT(value[2])) {
code = atoi(value);
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: %s",
code, reply_name, reply_class, cmd_text));
}
if (STREQUAL(value, "OK", cmd_len) || STREQUAL(value, "RELAY", cmd_len))
return (SMTPD_CHECK_OK);
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, smtpd_check_reject(state, MAIL_ERROR_SOFTWARE,
"451 Server configuration error"));
}
if (state->recursion > 100) {
msg_warn("SMTPD access map %s entry %s causes unreasonable recursion",
table, value);
longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_SOFTWARE,
"451 Server configuration error"));
}
#define ADDROF(x) ((char *) &(x))
restrictions = argv_split(value, " \t\r\n,");
memcpy(ADDROF(savebuf), ADDROF(smtpd_check_buf), sizeof(savebuf));
status = setjmp(smtpd_check_buf);
if (status != 0) {
argv_free(restrictions);
memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf),
sizeof(smtpd_check_buf));
longjmp(smtpd_check_buf, status);
}
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);
}
static int check_access(SMTPD_STATE *state, const char *table, const char *name,
int flags, int *found, const char *reply_name,
const char *reply_class, const char *def_acl)
{
char *myname = "check_access";
char *low_name = lowercase(mystrdup(name));
const char *value;
DICT *dict;
#define CHK_ACCESS_RETURN(x,y) { *found = y; myfree(low_name); return(x); }
#define FULL 0
#define PARTIAL DICT_FLAG_FIXED
#define FOUND 1
#define MISSED 0
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), FOUND);
if (dict_errno != 0)
msg_fatal("%s: table lookup problem", table);
}
CHK_ACCESS_RETURN(SMTPD_CHECK_DUNNO, MISSED);
}
static int check_domain_access(SMTPD_STATE *state, const char *table,
const char *domain, int flags,
int *found, const char *reply_name,
const char *reply_class,
const 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);
#define CHK_DOMAIN_RETURN(x,y) { *found = y; myfree(low_domain); return(x); }
if ((dict = dict_handle(table)) == 0)
msg_panic("%s: dictionary not found: %s", myname, table);
for (name = low_domain; *name != 0; name = next) {
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), FOUND);
if (dict_errno != 0)
msg_fatal("%s: table lookup problem", table);
}
if ((next = strchr(name + 1, '.')) == 0)
break;
if (access_parent_style == MATCH_FLAG_PARENT)
next += 1;
flags = PARTIAL;
}
CHK_DOMAIN_RETURN(SMTPD_CHECK_DUNNO, MISSED);
}
static int check_addr_access(SMTPD_STATE *state, const char *table,
const char *address, int flags,
int *found, const char *reply_name,
const char *reply_class,
const char *def_acl)
{
char *myname = "check_addr_access";
char *addr;
const char *value;
DICT *dict;
if (msg_verbose)
msg_info("%s: %s", myname, address);
#define CHK_ADDR_RETURN(x,y) { *found = y; return(x); }
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)
CHK_ADDR_RETURN(check_table_result(state, table, value, address,
reply_name, reply_class,
def_acl), FOUND);
if (dict_errno != 0)
msg_fatal("%s: table lookup problem", table);
}
flags = PARTIAL;
} while (split_at_right(addr, '.'));
CHK_ADDR_RETURN(SMTPD_CHECK_DUNNO, MISSED);
}
static int check_namadr_access(SMTPD_STATE *state, const char *table,
const char *name, const char *addr,
int flags, int *found,
const char *reply_name,
const char *reply_class,
const char *def_acl)
{
char *myname = "check_namadr_access";
int status;
if (msg_verbose)
msg_info("%s: name %s addr %s", myname, name, addr);
if ((status = check_domain_access(state, table, name, flags,
found, reply_name, reply_class,
def_acl)) != 0 || *found)
return (status);
if ((status = check_addr_access(state, table, addr, flags,
found, reply_name, reply_class,
def_acl)) != 0 || *found)
return (status);
return (SMTPD_CHECK_DUNNO);
}
static int check_mail_access(SMTPD_STATE *state, const char *table,
const char *addr, int *found,
const char *reply_name,
const char *reply_class,
const char *def_acl)
{
char *myname = "check_mail_access";
const RESOLVE_REPLY *reply;
const char *domain;
int status;
char *local_at;
char *bare_addr;
char *bare_at;
if (msg_verbose)
msg_info("%s: %s", myname, addr);
reply = (const RESOLVE_REPLY *) ctable_locate(smtpd_resolve_cache, addr);
if (reply->flags & RESOLVE_FLAG_FAIL)
reject_dict_retry(state, addr);
if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) {
msg_warn("%s: no @domain in address: %s", myname,
CONST_STR(reply->recipient));
return (0);
}
domain += 1;
if (*var_rcpt_delim == 0) {
bare_addr = 0;
} else {
bare_addr = strip_addr(addr, (char **) 0, *var_rcpt_delim);
}
#define CHECK_MAIL_ACCESS_RETURN(x) \
{ if (bare_addr) myfree(bare_addr); return(x); }
#define SUSPICIOUS(reply, reply_class) \
(var_allow_untrust_route == 0 \
&& (reply->flags & RESOLVE_FLAG_ROUTED) \
&& strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0)
if ((status = check_access(state, table, CONST_STR(reply->recipient), FULL,
found, reply_name, reply_class, def_acl)) != 0
|| *found)
CHECK_MAIL_ACCESS_RETURN(status == SMTPD_CHECK_OK
&& SUSPICIOUS(reply, reply_class) ?
SMTPD_CHECK_DUNNO : status);
if (bare_addr)
if ((status = check_access(state, table, bare_addr, PARTIAL,
found, reply_name, reply_class, def_acl)) != 0
|| *found)
CHECK_MAIL_ACCESS_RETURN(status == SMTPD_CHECK_OK
&& SUSPICIOUS(reply, reply_class) ?
SMTPD_CHECK_DUNNO : status);
if ((status = check_domain_access(state, table, domain, PARTIAL,
found, reply_name, reply_class, def_acl)) != 0
|| *found)
CHECK_MAIL_ACCESS_RETURN(status == SMTPD_CHECK_OK
&& SUSPICIOUS(reply, reply_class) ?
SMTPD_CHECK_DUNNO : status);
local_at = mystrndup(CONST_STR(reply->recipient),
domain - CONST_STR(reply->recipient));
status = check_access(state, table, local_at, PARTIAL, found,
reply_name, reply_class, def_acl);
myfree(local_at);
if (status != 0 || *found)
CHECK_MAIL_ACCESS_RETURN(status == SMTPD_CHECK_OK
&& SUSPICIOUS(reply, reply_class) ?
SMTPD_CHECK_DUNNO : status);
if (bare_addr) {
bare_at = strrchr(bare_addr, '@');
local_at = (bare_at ? mystrndup(bare_addr, bare_at + 1 - bare_addr) :
mystrdup(bare_addr));
status = check_access(state, table, local_at, PARTIAL, found,
reply_name, reply_class, def_acl);
myfree(local_at);
if (status != 0 || *found)
CHECK_MAIL_ACCESS_RETURN(status == SMTPD_CHECK_OK
&& SUSPICIOUS(reply, reply_class) ?
SMTPD_CHECK_DUNNO : status);
}
CHECK_MAIL_ACCESS_RETURN(SMTPD_CHECK_DUNNO);
}
static void smtpd_expand_unknown(const char *name)
{
msg_warn("unknown macro name \"%s\" in expansion request", name);
}
static const char *smtpd_expand_addr(VSTRING *buf, const char *addr,
const char *name, int prefix_len)
{
const char *p;
const char *suffix;
if (addr == 0)
return ("");
suffix = name + prefix_len;
if (*suffix == 0) {
if (*addr)
return (addr);
else
return ("<>");
}
#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0)
else if (STREQ(suffix, MAIL_ATTR_S_NAME)) {
if (*addr) {
if ((p = strrchr(addr, '@')) != 0) {
vstring_strncpy(buf, addr, p - addr);
return (STR(buf));
} else {
return (addr);
}
} else
return ("<>");
}
else if (STREQ(suffix, MAIL_ATTR_S_DOMAIN)) {
if (*addr) {
if ((p = strrchr(addr, '@')) != 0) {
return (p + 1);
} else {
return ("");
}
} else
return ("");
}
else {
smtpd_expand_unknown(name);
return (0);
}
}
static const char *smtpd_expand_lookup(const char *name, int unused_mode,
char *context)
{
SMTPD_STATE *state = (SMTPD_STATE *) context;
if (state->expand_buf == 0)
state->expand_buf = vstring_alloc(10);
if (msg_verbose > 1)
msg_info("smtpd_expand_lookup: ${%s}", name);
#define STREQN(x,y,n) (*(x) == *(y) && strncmp((x), (y), (n)) == 0)
#define CONST_LEN(x) (sizeof(x) - 1)
if (STREQ(name, MAIL_ATTR_CLIENT)) {
return (state->namaddr);
} else if (STREQ(name, MAIL_ATTR_CLIENT_ADDR)) {
return (state->addr);
} else if (STREQ(name, MAIL_ATTR_CLIENT_NAME)) {
return (state->name);
} else if (STREQ(name, MAIL_ATTR_HELO_NAME)) {
return (state->helo_name ? state->helo_name : "");
} else if (STREQN(name, MAIL_ATTR_SENDER, CONST_LEN(MAIL_ATTR_SENDER))) {
return (smtpd_expand_addr(state->expand_buf, state->sender,
name, CONST_LEN(MAIL_ATTR_SENDER)));
} else if (STREQN(name, MAIL_ATTR_RECIP, CONST_LEN(MAIL_ATTR_RECIP))) {
return (smtpd_expand_addr(state->expand_buf, state->recipient,
name, CONST_LEN(MAIL_ATTR_RECIP)));
} else {
smtpd_expand_unknown(name);
return (0);
}
}
static void *rbl_pagein(const char *query, void *unused_context)
{
DNS_RR *txt_list;
VSTRING *why;
int dns_status;
SMTPD_RBL_STATE *rbl;
why = vstring_alloc(10);
dns_status = dns_lookup(query, T_A, 0, (DNS_RR **) 0,
(VSTRING *) 0, why);
if (dns_status != DNS_OK && dns_status != DNS_NOTFOUND)
msg_warn("%s: RBL lookup error: %s", query, STR(why));
vstring_free(why);
if (dns_status != DNS_OK)
return (0);
rbl = (SMTPD_RBL_STATE *) mymalloc(sizeof(*rbl));
if (dns_lookup(query, T_TXT, 0, &txt_list,
(VSTRING *) 0, (VSTRING *) 0) == DNS_OK) {
rbl->txt = mystrndup(txt_list->data, 512);
dns_rr_free(txt_list);
} else
rbl->txt = mystrdup("");
return ((void *) rbl);
}
static void rbl_pageout(void *data, void *unused_context)
{
SMTPD_RBL_STATE *rbl = (SMTPD_RBL_STATE *) data;
if (rbl != 0) {
if (rbl->txt)
myfree(rbl->txt);
myfree((char *) rbl);
}
}
static const char *rbl_expand_lookup(const char *name, int mode,
char *context)
{
SMTPD_RBL_EXPAND_CONTEXT *rbl_exp = (SMTPD_RBL_EXPAND_CONTEXT *) context;
SMTPD_RBL_STATE *rbl = rbl_exp->rbl_state;
SMTPD_STATE *state = rbl_exp->state;
if (state->expand_buf == 0)
state->expand_buf = vstring_alloc(10);
if (msg_verbose > 1)
msg_info("rbl_expand_lookup: ${%s}", name);
if (STREQ(name, MAIL_ATTR_RBL_CODE)) {
vstring_sprintf(state->expand_buf, "%d", var_maps_rbl_code);
return (STR(state->expand_buf));
} else if (STREQ(name, MAIL_ATTR_RBL_DOMAIN)) {
return (rbl_exp->domain);
} else if (STREQ(name, MAIL_ATTR_RBL_REASON)) {
return (rbl->txt);
} else if (STREQ(name, MAIL_ATTR_RBL_TXT)) {
return (rbl->txt);
} else if (STREQ(name, MAIL_ATTR_RBL_WHAT)) {
return (rbl_exp->what);
} else if (STREQ(name, MAIL_ATTR_RBL_CLASS)) {
return (rbl_exp->class);
} else {
return (smtpd_expand_lookup(name, mode, (char *) state));
}
}
static int rbl_reject_reply(SMTPD_STATE *state, SMTPD_RBL_STATE *rbl,
const char *rbl_domain,
const char *what,
const char *reply_class)
{
const char *myname = "rbl_reject_reply";
VSTRING *why = 0;
const char *template = 0;
char *low_name;
SMTPD_RBL_EXPAND_CONTEXT rbl_exp;
int result;
if (*var_rbl_reply_maps) {
low_name = lowercase(mystrdup(rbl_domain));
template = maps_find(rbl_reply_maps, low_name, 0);
myfree(low_name);
}
why = vstring_alloc(100);
rbl_exp.state = state;
rbl_exp.rbl_state = rbl;
rbl_exp.domain = rbl_domain;
rbl_exp.what = what;
rbl_exp.class = reply_class;
for (;;) {
if (template == 0)
template = var_def_rbl_reply;
if (mac_expand(why, template, MAC_EXP_FLAG_NONE,
STR(expand_filter), rbl_expand_lookup,
(char *) &rbl_exp) == 0)
break;
if (template == var_def_rbl_reply)
msg_fatal("%s: bad default rbl reply template: %s",
myname, var_def_rbl_reply);
msg_warn("%s: bad rbl reply template for domain %s: %s",
myname, rbl_domain, template);
template = 0;
}
result = smtpd_check_reject(state, MAIL_ERROR_POLICY, "%s", STR(why));
vstring_free(why);
return (result);
}
static int reject_rbl_addr(SMTPD_STATE *state, const char *rbl_domain,
const char *addr, const char *reply_class)
{
char *myname = "reject_rbl";
ARGV *octets;
VSTRING *query;
int i;
SMTPD_RBL_STATE *rbl;
if (msg_verbose)
msg_info("%s: %s %s", myname, reply_class, addr);
#ifdef INET6
if (inet_pton(AF_INET, addr, &a) != 1)
return SMTPD_CHECK_DUNNO;
#endif
query = vstring_alloc(100);
octets = argv_split(addr, ".");
for (i = octets->argc - 1; i >= 0; i--) {
vstring_strcat(query, octets->argv[i]);
vstring_strcat(query, ".");
}
argv_free(octets);
vstring_strcat(query, rbl_domain);
rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query));
vstring_free(query);
if (rbl == 0) {
return (SMTPD_CHECK_DUNNO);
} else {
return (rbl_reject_reply(state, rbl, rbl_domain, addr, reply_class));
}
}
static int reject_rbl_domain(SMTPD_STATE *state, const char *rbl_domain,
const char *what, const char *reply_class)
{
char *myname = "reject_rbl_domain";
VSTRING *query;
SMTPD_RBL_STATE *rbl;
const char *domain;
if (msg_verbose)
msg_info("%s: %s %s", myname, reply_class, what);
if ((domain = strrchr(what, '@')) != 0) {
domain += 1;
if (domain[0] == '[')
return (SMTPD_CHECK_DUNNO);
} else
domain = what;
if (domain[0] == 0)
return (SMTPD_CHECK_DUNNO);
query = vstring_alloc(100);
vstring_sprintf(query, "%s.%s", domain, rbl_domain);
rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query));
vstring_free(query);
if (rbl == 0) {
return (SMTPD_CHECK_DUNNO);
} else {
return (rbl_reject_reply(state, rbl, rbl_domain, what, reply_class));
}
}
static int reject_maps_rbl(SMTPD_STATE *state)
{
char *myname = "reject_maps_rbl";
char *saved_domains = mystrdup(var_maps_rbl_domains);
char *bp = saved_domains;
char *rbl_domain;
int result = SMTPD_CHECK_DUNNO;
static int warned;
if (msg_verbose)
msg_info("%s: %s", myname, state->addr);
if (warned == 0) {
warned++;
msg_warn("restriction %s is going away. Please use %s <domain> instead",
REJECT_MAPS_RBL, REJECT_RBL_CLIENT);
}
while ((rbl_domain = mystrtok(&bp, " \t\r\n,")) != 0) {
result = reject_rbl_addr(state, rbl_domain, state->addr,
SMTPD_NAME_CLIENT);
if (result != SMTPD_CHECK_DUNNO)
break;
}
myfree(saved_domains);
return (result);
}
static int reject_sender_login_mismatch(SMTPD_STATE *state, const char *sender)
{
const RESOLVE_REPLY *reply;
const char *login = 0;
const char *owner = 0;
reply = (const RESOLVE_REPLY *) ctable_locate(smtpd_resolve_cache, sender);
if (reply->flags & RESOLVE_FLAG_FAIL)
reject_dict_retry(state, sender);
owner = check_mail_addr_find(state, sender, smtpd_sender_login_maps,
STR(reply->recipient), (char **) 0);
#ifdef USE_SASL_AUTH
if (var_smtpd_sasl_enable && state->sasl_username != 0)
login = state->sasl_username;
#endif
if (login) {
if (owner == 0 || strcasecmp(login, owner) != 0)
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"553 <%s>: Sender address rejected: not owned by user %s",
sender, login));
} else {
if (owner)
return (smtpd_check_reject(state, MAIL_ERROR_POLICY,
"553 <%s>: Sender address rejected: not logged in as owner",
sender));
}
return (SMTPD_CHECK_DUNNO);
}
static int is_map_command(SMTPD_STATE *state, const char *name,
const char *command, char ***argp)
{
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, smtpd_check_reject(state, MAIL_ERROR_SOFTWARE,
"451 Server configuration error"));
} else {
return (1);
}
}
static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
const char *reply_name,
const char *reply_class,
const char *def_acl)
{
char *myname = "generic_checks";
char **cpp;
const char *name;
int status = 0;
ARGV *list;
int found;
int saved_recursion = state->recursion++;
if (msg_verbose)
msg_info("%s: START", myname);
for (cpp = restrictions->argv; (name = *cpp) != 0; cpp++) {
if (state->discard != 0)
break;
if (msg_verbose)
msg_info("%s: name=%s", myname, name);
if (strcasecmp(name, WARN_IF_REJECT) == 0) {
if (state->warn_if_reject == 0)
state->warn_if_reject = state->recursion;
continue;
}
#define NO_DEF_ACL 0
if (strchr(name, ':') != 0) {
if (def_acl == NO_DEF_ACL) {
msg_warn("specify one of (%s, %s, %s, %s, %s) before %s restriction \"%s\"",
CHECK_CLIENT_ACL, CHECK_HELO_ACL, CHECK_SENDER_ACL,
CHECK_RECIP_ACL, CHECK_ETRN_ACL, reply_class, name);
longjmp(smtpd_check_buf, smtpd_check_reject(state,
MAIL_ERROR_SOFTWARE, "451 Server configuration error"));
}
name = def_acl;
cpp -= 1;
}
if (strcasecmp(name, PERMIT_ALL) == 0) {
status = SMTPD_CHECK_OK;
if (cpp[1] != 0 && state->warn_if_reject == 0)
msg_warn("restriction `%s' after `%s' is ignored",
cpp[1], PERMIT_ALL);
} else if (strcasecmp(name, DEFER_ALL) == 0) {
status = smtpd_check_reject(state, MAIL_ERROR_POLICY,
"%d <%s>: %s rejected: Try again later",
var_defer_code, reply_name, reply_class);
if (cpp[1] != 0 && state->warn_if_reject == 0)
msg_warn("restriction `%s' after `%s' is ignored",
cpp[1], DEFER_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 && state->warn_if_reject == 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, reply_name, reply_class);
} else if (strcasecmp(name, DEFER_IF_PERMIT) == 0) {
DEFER_IF_PERMIT2(state, MAIL_ERROR_POLICY,
"450 <%s>: %s rejected: defer_if_permit requested",
reply_name, reply_class);
} else if (strcasecmp(name, DEFER_IF_REJECT) == 0) {
DEFER_IF_REJECT2(state, MAIL_ERROR_POLICY,
"450 <%s>: %s rejected: defer_if_reject requested",
reply_name, reply_class);
}
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(state, name, CHECK_CLIENT_ACL, &cpp)) {
status = check_namadr_access(state, *cpp, state->name, state->addr,
FULL, &found, state->namaddr,
SMTPD_NAME_CLIENT, def_acl);
} else if (strcasecmp(name, REJECT_MAPS_RBL) == 0) {
status = reject_maps_rbl(state);
} else if (strcasecmp(name, REJECT_RBL_CLIENT) == 0
|| strcasecmp(name, REJECT_RBL) == 0) {
if (cpp[1] == 0)
msg_warn("restriction %s requires domain name argument", name);
else
status = reject_rbl_addr(state, *(cpp += 1), state->addr,
SMTPD_NAME_CLIENT);
} else if (strcasecmp(name, REJECT_RHSBL_CLIENT) == 0) {
if (cpp[1] == 0)
msg_warn("restriction %s requires domain name argument",
name);
else {
cpp += 1;
if (strcasecmp(state->name, "unknown") != 0)
status = reject_rbl_domain(state, *cpp, state->name,
SMTPD_NAME_CLIENT);
}
}
else if (is_map_command(state, name, CHECK_HELO_ACL, &cpp)) {
if (state->helo_name)
status = check_domain_access(state, *cpp, state->helo_name,
FULL, &found, 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) {
msg_warn("restriction %s is deprecated. Use %s instead",
PERMIT_NAKED_IP_ADDR, PERMIT_MYNETWORKS);
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);
}
}
else if (is_map_command(state, name, CHECK_SENDER_ACL, &cpp)) {
if (state->sender && *state->sender)
status = check_mail_access(state, *cpp, state->sender,
&found, state->sender,
SMTPD_NAME_SENDER, def_acl);
if (state->sender && !*state->sender)
status = check_access(state, *cpp, var_smtpd_null_key, FULL,
&found, 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);
} else if (strcasecmp(name, REJECT_SENDER_LOGIN_MISMATCH) == 0) {
if (state->sender && *state->sender)
status = reject_sender_login_mismatch(state, state->sender);
} else if (strcasecmp(name, REJECT_RHSBL_SENDER) == 0) {
if (cpp[1] == 0)
msg_warn("restriction %s requires domain name argument", name);
else {
cpp += 1;
if (state->sender && *state->sender)
status = reject_rbl_domain(state, *cpp, state->sender,
SMTPD_NAME_SENDER);
}
}
else if (is_map_command(state, name, CHECK_RECIP_ACL, &cpp)) {
if (state->recipient)
status = check_mail_access(state, *cpp, state->recipient,
&found, 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,
state->recipient, SMTPD_NAME_RECIPIENT);
} else if (strcasecmp(name, PERMIT_AUTH_DEST) == 0) {
if (state->recipient)
status = permit_auth_destination(state, 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 && state->warn_if_reject == 0)
msg_warn("restriction `%s' after `%s' is ignored",
cpp[1], CHECK_RELAY_DOMAINS);
} else if (strcasecmp(name, PERMIT_SASL_AUTH) == 0) {
if (var_smtpd_sasl_enable)
#ifdef USE_SASL_AUTH
status = permit_sasl_auth(state,
SMTPD_CHECK_OK, SMTPD_CHECK_DUNNO);
#else
msg_warn("restriction `%s' ignored: no SASL support", name);
#endif
#ifdef HAS_SSL
} else if (strcasecmp(name, PERMIT_TLS_ALL_CLIENTCERTS) == 0) {
status = permit_tls_clientcerts(state, 1);
} else if (strcasecmp(name, PERMIT_TLS_CLIENTCERTS) == 0) {
status = permit_tls_clientcerts(state, 0);
#endif
} 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);
} else if (strcasecmp(name, REJECT_RHSBL_RECIPIENT) == 0) {
if (cpp[1] == 0)
msg_warn("restriction %s requires domain name argument", name);
else {
cpp += 1;
if (state->recipient)
status = reject_rbl_domain(state, *cpp, state->recipient,
SMTPD_NAME_RECIPIENT);
}
} else if (strcasecmp(name, CHECK_RCPT_MAPS) == 0) {
if (state->recipient && *state->recipient)
status = check_rcpt_maps(state, state->recipient);
}
else if (is_map_command(state, name, CHECK_ETRN_ACL, &cpp)) {
if (state->etrn_name)
status = check_domain_access(state, *cpp, state->etrn_name,
FULL, &found, state->etrn_name,
SMTPD_NAME_ETRN, def_acl);
}
else if ((list = (ARGV *) htable_find(smtpd_rest_classes, name)) != 0) {
status = generic_checks(state, list, reply_name,
reply_class, def_acl);
}
else {
msg_warn("unknown smtpd restriction: \"%s\"", name);
longjmp(smtpd_check_buf, smtpd_check_reject(state,
MAIL_ERROR_SOFTWARE, "451 Server configuration error"));
}
if (msg_verbose)
msg_info("%s: name=%s status=%d", myname, name, status);
if (state->warn_if_reject >= state->recursion)
state->warn_if_reject = 0;
if (status != 0)
break;
if (state->defer_if_permit.active && state->defer_if_reject.active)
break;
}
if (msg_verbose && name == 0)
msg_info("%s: END", myname);
state->recursion = saved_recursion;
return (status);
}
char *smtpd_check_client(SMTPD_STATE *state)
{
int status;
if (state->name == 0 || state->addr == 0)
return (0);
#define SMTPD_CHECK_RESET() { \
state->recursion = 0; \
state->warn_if_reject = 0; \
state->defer_if_reject.active = 0; \
}
state->defer_if_permit.active = 0;
SMTPD_CHECK_RESET();
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);
state->defer_if_permit_client = state->defer_if_permit.active;
return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
char *smtpd_check_helo(SMTPD_STATE *state, char *helohost)
{
int status;
char *saved_helo;
if (helohost == 0)
return (0);
#define SMTPD_CHECK_PUSH(backup, current, new) { \
backup = current; \
current = (new ? mystrdup(new) : 0); \
}
#define SMTPD_CHECK_POP(current, backup) { \
if (current) 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); \
}
state->defer_if_permit.active = state->defer_if_permit_client;
SMTPD_CHECK_RESET();
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);
state->defer_if_permit_helo = state->defer_if_permit.active;
SMTPD_CHECK_HELO_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
char *smtpd_check_mail(SMTPD_STATE *state, char *sender)
{
int status;
char *saved_sender;
if (sender == 0)
return (0);
SMTPD_CHECK_PUSH(saved_sender, state->sender, sender);
#define SMTPD_CHECK_MAIL_RETURN(x) { \
SMTPD_CHECK_POP(state->sender, saved_sender); \
return (x); \
}
state->defer_if_permit.active = state->defer_if_permit_client
| state->defer_if_permit_helo;
SMTPD_CHECK_RESET();
status = setjmp(smtpd_check_buf);
if (status == 0 && mail_restrctions->argc)
status = generic_checks(state, mail_restrctions, sender,
SMTPD_NAME_SENDER, CHECK_SENDER_ACL);
state->defer_if_permit_sender = state->defer_if_permit.active;
SMTPD_CHECK_MAIL_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
char *smtpd_check_rcpt(SMTPD_STATE *state, char *recipient)
{
int status;
char *saved_recipient;
char *err;
if (recipient == 0)
return (0);
if (strcasecmp(recipient, "postmaster") == 0)
return (0);
SMTPD_CHECK_PUSH(saved_recipient, state->recipient, recipient);
#define SMTPD_CHECK_RCPT_RETURN(x) { \
SMTPD_CHECK_POP(state->recipient, saved_recipient); \
return (x); \
}
state->rcptmap_checked = 0;
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);
state->defer_if_permit.active = state->defer_if_permit_sender;
SMTPD_CHECK_RESET();
status = setjmp(smtpd_check_buf);
if (status == 0 && rcpt_restrctions->argc)
status = generic_checks(state, rcpt_restrctions,
recipient, SMTPD_NAME_RECIPIENT, CHECK_RECIP_ACL);
if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active)
status = smtpd_check_reject(state, state->defer_if_permit.class,
"%s", STR(state->defer_if_permit.reason));
if (status != SMTPD_CHECK_REJECT && state->rcptmap_checked == 0
&& state->discard == 0)
status = check_rcpt_maps(state, recipient);
SMTPD_CHECK_RCPT_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
char *smtpd_check_etrn(SMTPD_STATE *state, char *domain)
{
int status;
char *saved_etrn_name;
char *err;
if (domain == 0)
return (0);
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); \
}
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);
state->defer_if_permit.active = state->defer_if_permit_client
| state->defer_if_permit_helo;
SMTPD_CHECK_RESET();
status = setjmp(smtpd_check_buf);
if (status == 0 && etrn_restrctions->argc)
status = generic_checks(state, etrn_restrctions, domain,
SMTPD_NAME_ETRN, CHECK_ETRN_ACL);
if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active)
status = smtpd_check_reject(state, state->defer_if_permit.class,
"%s", STR(state->defer_if_permit.reason));
SMTPD_CHECK_ETRN_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
static int check_rcpt_maps(SMTPD_STATE *state, const char *recipient)
{
const RESOLVE_REPLY *reply;
if (state->rcptmap_checked == 1)
return (0);
state->rcptmap_checked = 1;
reply = (const RESOLVE_REPLY *)
ctable_locate(smtpd_resolve_cache, recipient);
if (reply->flags & RESOLVE_FLAG_FAIL)
reject_dict_retry(state, recipient);
#define MATCH(map, rcpt) \
check_mail_addr_find(state, recipient, map, rcpt, (char **) 0)
#define NOMATCH(map, rcpt) (MATCH(map, rcpt) == 0)
#define NOMATCHV8(map, rcpt) \
(checkv8_maps_find(state, recipient, map, rcpt) == 0)
if (MATCH(rcpt_canon_maps, CONST_STR(reply->recipient))
|| MATCH(canonical_maps, CONST_STR(reply->recipient))
|| MATCH(virt_alias_maps, CONST_STR(reply->recipient)))
return (0);
if (strcmp(STR(reply->transport), MAIL_SERVICE_ERROR) == 0)
return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
"%d <%s>: %s",
(reply->flags & RESOLVE_CLASS_ALIAS) ?
var_virt_alias_code : 550,
recipient, STR(reply->nexthop)));
#define MATCH_LEFT(l, r, n) (strncasecmp((l), (r), (n)) == 0 && (r)[n] == '@')
if ((reply->flags & RESOLVE_CLASS_LOCAL)
&& *var_local_rcpt_maps
&& !MATCH_LEFT(var_double_bounce_sender, CONST_STR(reply->recipient),
strlen(var_double_bounce_sender))
&& !MATCH_LEFT(MAIL_ADDR_POSTMASTER, CONST_STR(reply->recipient),
strlen(MAIL_ADDR_POSTMASTER))
&& !MATCH_LEFT(MAIL_ADDR_MAIL_DAEMON, CONST_STR(reply->recipient),
strlen(MAIL_ADDR_MAIL_DAEMON))
&& NOMATCH(local_rcpt_maps, CONST_STR(reply->recipient)))
return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
"%d <%s>: User unknown%s",
var_local_rcpt_code, recipient,
var_show_unk_rcpt_table ?
" in local recipient table" : ""));
if ((reply->flags & RESOLVE_CLASS_VIRTUAL)
&& *var_virt_mailbox_maps
&& NOMATCHV8(virt_mailbox_maps, CONST_STR(reply->recipient)))
return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
"%d <%s>: User unknown%s",
var_virt_mailbox_code, recipient,
var_show_unk_rcpt_table ?
" in virtual mailbox table" : ""));
if ((reply->flags & RESOLVE_CLASS_RELAY)
&& *var_relay_rcpt_maps
&& NOMATCH(relay_rcpt_maps, CONST_STR(reply->recipient)))
return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE,
"%d <%s>: User unknown%s",
var_relay_rcpt_code, recipient,
var_show_unk_rcpt_table ?
" in relay recipient table" : ""));
return (0);
}
char *smtpd_check_size(SMTPD_STATE *state, off_t size)
{
char *myname = "smtpd_check_size";
struct fsspace fsbuf;
int status;
SMTPD_CHECK_RESET();
if ((status = setjmp(smtpd_check_buf)) != 0)
return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
#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);
}
char *smtpd_check_data(SMTPD_STATE *state)
{
int status;
char *saved_recipient;
if (state->rcpt_count > 1) {
saved_recipient = state->recipient;
state->recipient = 0;
}
state->defer_if_permit.active = 0;
SMTPD_CHECK_RESET();
status = setjmp(smtpd_check_buf);
if (status == 0 && data_restrctions->argc)
status = generic_checks(state, data_restrctions,
"DATA", SMTPD_NAME_DATA, NO_DEF_ACL);
if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active)
status = smtpd_check_reject(state, state->defer_if_permit.class,
"%s", STR(state->defer_if_permit.reason));
if (state->rcpt_count > 1)
state->recipient = saved_recipient;
return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0);
}
#ifdef TEST
#include <stdlib.h>
#include <msg_vstream.h>
#include <vstring_vstream.h>
#include <mail_conf.h>
#include <smtpd_chat.h>
char *var_client_checks = "";
char *var_helo_checks = "";
char *var_mail_checks = "";
char *var_rcpt_checks = "";
char *var_etrn_checks = "";
char *var_data_checks = "";
char *var_relay_domains = "";
char *var_relay_ccerts = "";
char *var_mynetworks = "";
char *var_notify_classes = "";
char *var_maps_rbl_domains;
char *var_myorigin;
char *var_mydest;
char *var_inet_interfaces;
char *var_rcpt_delim;
char *var_rest_classes;
char *var_alias_maps;
char *var_rcpt_canon_maps;
char *var_canonical_maps;
char *var_virt_alias_maps;
char *var_virt_alias_doms;
char *var_virt_mailbox_maps;
char *var_virt_mailbox_doms;
char *var_local_rcpt_maps;
char *var_perm_mx_networks;
char *var_par_dom_match;
char *var_smtpd_null_key;
char *var_smtpd_snd_auth_maps;
char *var_double_bounce_sender;
char *var_rbl_reply_maps;
char *var_smtpd_exp_filter;
char *var_def_rbl_reply;
char *var_relay_rcpt_maps;
typedef struct {
char *name;
char *defval;
char **target;
} STRING_TABLE;
#undef DEF_LOCAL_RCPT_MAPS
#define DEF_LOCAL_RCPT_MAPS ""
#undef DEF_VIRT_ALIAS_MAPS
#define DEF_VIRT_ALIAS_MAPS ""
static STRING_TABLE string_table[] = {
VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains,
VAR_MYORIGIN, DEF_MYORIGIN, &var_myorigin,
VAR_MYDEST, DEF_MYDEST, &var_mydest,
VAR_INET_INTERFACES, DEF_INET_INTERFACES, &var_inet_interfaces,
VAR_RCPT_DELIM, DEF_RCPT_DELIM, &var_rcpt_delim,
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_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps,
VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms,
VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps,
VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms,
VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps,
VAR_PERM_MX_NETWORKS, DEF_PERM_MX_NETWORKS, &var_perm_mx_networks,
VAR_PAR_DOM_MATCH, DEF_PAR_DOM_MATCH, &var_par_dom_match,
VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps,
VAR_SMTPD_NULL_KEY, DEF_SMTPD_NULL_KEY, &var_smtpd_null_key,
VAR_DOUBLE_BOUNCE, DEF_DOUBLE_BOUNCE, &var_double_bounce_sender,
VAR_RBL_REPLY_MAPS, DEF_RBL_REPLY_MAPS, &var_rbl_reply_maps,
VAR_SMTPD_EXP_FILTER, DEF_SMTPD_EXP_FILTER, &var_smtpd_exp_filter,
VAR_DEF_RBL_REPLY, DEF_DEF_RBL_REPLY, &var_def_rbl_reply,
VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps,
0,
};
static void string_init(void)
{
STRING_TABLE *sp;
for (sp = string_table; sp->name; sp++)
sp->target[0] = mystrdup(sp->defval);
}
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);
}
int var_queue_minfree;
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_defer_code;
int var_non_fqdn_code;
int var_smtpd_delay_reject;
int var_allow_untrust_route;
int var_local_rcpt_code;
int var_relay_rcpt_code;
int var_virt_mailbox_code;
int var_virt_alias_code;
int var_show_unk_rcpt_table;
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_DEFER_CODE, DEF_DEFER_CODE, &var_defer_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,
VAR_LOCAL_RCPT_CODE, DEF_LOCAL_RCPT_CODE, &var_local_rcpt_code,
VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code,
VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code,
VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code,
VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table,
0,
};
static void int_init(void)
{
INT_TABLE *sp;
for (sp = int_table; sp->name; sp++)
sp->target[0] = sp->defval;
}
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);
}
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,
};
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);
}
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);
}
void resolve_clnt_init(RESOLVE_REPLY *reply)
{
reply->flags = 0;
reply->transport = vstring_alloc(100);
reply->nexthop = vstring_alloc(100);
reply->recipient = vstring_alloc(100);
}
void resolve_clnt_free(RESOLVE_REPLY *reply)
{
vstring_free(reply->transport);
vstring_free(reply->nexthop);
vstring_free(reply->recipient);
}
bool var_smtpd_sasl_enable = 0;
#ifdef USE_SASL_AUTH
void smtpd_sasl_connect(SMTPD_STATE *state)
{
msg_panic("smtpd_sasl_connect was called");
}
void smtpd_sasl_disconnect(SMTPD_STATE *state)
{
msg_panic("smtpd_sasl_disconnect was called");
}
int permit_sasl_auth(SMTPD_STATE *state, int ifyes, int ifnot)
{
return (ifnot);
}
#endif
VSTRING *canon_addr_internal(VSTRING *result, const char *addr)
{
if (addr == STR(result))
msg_panic("canon_addr_internal: result clobbers input");
if (*addr && strchr(addr, '@') == 0)
msg_fatal("%s: address rewriting is disabled", addr);
vstring_strcpy(result, addr);
}
void resolve_clnt_query(const char *addr, RESOLVE_REPLY *reply)
{
const char *domain;
if (addr == CONST_STR(reply->recipient))
msg_panic("resolve_clnt_query: result clobbers input");
if (strchr(addr, '%'))
msg_fatal("%s: address rewriting is disabled", addr);
if ((domain = strrchr(addr, '@')) == 0)
msg_fatal("%s: unqualified address", addr);
domain += 1;
if (resolve_local(domain)) {
reply->flags = RESOLVE_CLASS_LOCAL;
vstring_strcpy(reply->transport, MAIL_SERVICE_LOCAL);
vstring_strcpy(reply->nexthop, domain);
} else if (string_list_match(virt_alias_doms, domain)) {
reply->flags = RESOLVE_CLASS_ALIAS;
vstring_strcpy(reply->transport, MAIL_SERVICE_ERROR);
vstring_strcpy(reply->nexthop, "user unknown");
} else if (string_list_match(virt_mailbox_doms, domain)) {
reply->flags = RESOLVE_CLASS_VIRTUAL;
vstring_strcpy(reply->transport, MAIL_SERVICE_VIRTUAL);
vstring_strcpy(reply->nexthop, domain);
} else if (domain_list_match(relay_domains, domain)) {
reply->flags = RESOLVE_CLASS_RELAY;
vstring_strcpy(reply->transport, MAIL_SERVICE_RELAY);
vstring_strcpy(reply->nexthop, domain);
} else {
reply->flags = RESOLVE_CLASS_DEFAULT;
vstring_strcpy(reply->transport, MAIL_SERVICE_SMTP);
vstring_strcpy(reply->nexthop, domain);
}
vstring_strcpy(reply->recipient, addr);
}
void smtpd_chat_reset(SMTPD_STATE *unused_state)
{
}
static NORETURN usage(char *myname)
{
msg_fatal("usage: %s", myname);
}
int main(int argc, char **argv)
{
VSTRING *buf = vstring_alloc(100);
SMTPD_STATE state;
ARGV *args;
char *bp;
char *resp;
char *addr;
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>";
while (vstring_fgets_nonl(buf, VSTREAM_IN) != 0) {
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");
resp = "bad command";
switch (args->argc) {
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;
#define UPDATE_MAPS(ptr, var, val, lock) \
{ if (ptr) maps_free(ptr); ptr = maps_create(var, val, lock); }
#define UPDATE_LIST(ptr, val) \
{ if (ptr) string_list_free(ptr); \
ptr = string_list_init(MATCH_FLAG_NONE, val); }
case 2:
if (strcasecmp(args->argv[0], VAR_VIRT_ALIAS_MAPS) == 0) {
UPDATE_STRING(var_virt_alias_maps, args->argv[1]);
UPDATE_MAPS(virt_alias_maps, VAR_VIRT_ALIAS_MAPS,
var_virt_alias_maps, DICT_FLAG_LOCK);
resp = 0;
break;
}
if (strcasecmp(args->argv[0], VAR_VIRT_ALIAS_DOMS) == 0) {
UPDATE_STRING(var_virt_alias_doms, args->argv[1]);
UPDATE_LIST(virt_alias_doms, var_virt_alias_doms);
resp = 0;
break;
}
if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_MAPS) == 0) {
UPDATE_STRING(var_virt_mailbox_maps, args->argv[1]);
UPDATE_MAPS(virt_mailbox_maps, VAR_VIRT_MAILBOX_MAPS,
var_virt_mailbox_maps, DICT_FLAG_LOCK);
resp = 0;
break;
}
if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_DOMS) == 0) {
UPDATE_STRING(var_virt_mailbox_doms, args->argv[1]);
UPDATE_LIST(virt_mailbox_doms, var_virt_mailbox_doms);
resp = 0;
break;
}
if (strcasecmp(args->argv[0], "local_recipient_maps") == 0) {
UPDATE_STRING(var_local_rcpt_maps, args->argv[1]);
UPDATE_MAPS(local_rcpt_maps, VAR_LOCAL_RCPT_MAPS,
var_local_rcpt_maps, DICT_FLAG_LOCK);
resp = 0;
break;
}
if (strcasecmp(args->argv[0], "relay_recipient_maps") == 0) {
UPDATE_STRING(var_relay_rcpt_maps, args->argv[1]);
UPDATE_MAPS(relay_rcpt_maps, VAR_RELAY_RCPT_MAPS,
var_relay_rcpt_maps, DICT_FLAG_LOCK);
resp = 0;
break;
}
if (strcasecmp(args->argv[0], "canonical_maps") == 0) {
UPDATE_STRING(var_canonical_maps, args->argv[1]);
UPDATE_MAPS(canonical_maps, VAR_CANONICAL_MAPS,
var_canonical_maps, DICT_FLAG_LOCK);
resp = 0;
break;
}
if (strcasecmp(args->argv[0], "rbl_reply_maps") == 0) {
UPDATE_STRING(var_rbl_reply_maps, args->argv[1]);
UPDATE_MAPS(rbl_reply_maps, VAR_RBL_REPLY_MAPS,
var_rbl_reply_maps, DICT_FLAG_LOCK);
resp = 0;
break;
}
if (strcasecmp(args->argv[0], "mynetworks") == 0) {
namadr_list_free(mynetworks);
mynetworks =
namadr_list_init(match_parent_style(VAR_MYNETWORKS),
args->argv[1]);
resp = 0;
break;
}
if (strcasecmp(args->argv[0], "relay_domains") == 0) {
domain_list_free(relay_domains);
relay_domains =
domain_list_init(match_parent_style(VAR_RELAY_DOMAINS),
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;
}
#define TRIM_ADDR(src, res) { \
if (*(res = src) == '<') { \
res += strlen(res) - 1; \
if (*res == '>') \
*res = 0; \
res = src + 1; \
} \
}
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";
TRIM_ADDR(args->argv[1], addr);
UPDATE_STRING(state.sender, addr);
resp = smtpd_check_mail(&state, addr);
} else if (strcasecmp(args->argv[0], "rcpt") == 0) {
state.where = "RCPT";
TRIM_ADDR(args->argv[1], addr);
resp = smtpd_check_rcpt(&state, addr);
}
break;
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