Skip to content

Commit

Permalink
net_smtp: Allow relaying messages through the BBS.
Browse files Browse the repository at this point in the history
The SMTP server can now be configured to relay
messages from certain servers for certain specified
domains, thus acting as a "smart host".

Also fixes a bug where capabilities were not parsed
when connecting to other MTAs, preventing STARTTLS
from working.
  • Loading branch information
InterLinked1 committed Oct 16, 2023
1 parent 6fdf142 commit aaf214c
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 32 deletions.
10 changes: 10 additions & 0 deletions configs/net_smtp.conf
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ addreceivedmsa=no ; Whether to include the sender's IP address in the Received
notifyextfirstmsg=yes ; Whether to send an email to a user's external email address when his/her mailbox is first created.
; Default is yes.

[authorized_relays] ; Define remote hosts that are allowed to relay outgoing mail using this server as a smart host.
; Configure each authorized relay as an IP/hostname/CIDR range and a list of domains or subdomains for which they are authorized to relay mail.
; If a connection matches multiple entries, the relay is allowed as long as it matches one of the entries.
; WARNING WARNING WARNING WARNING WARNING: Misconfiguration of this section may inadvertently turn your server into an open mail relay!
; The BBS will not perform any further checks of messages authorized by one of these entries and will simply relay messages as directed.
; If further verification of messages is required, the submitting SMTP server/client must do it (e.g. checking the sender is authorized to send as a particular user).
; Do not attempt to relay mail for domains that *THIS* server is not authorized to send as (otherwise failed SPF checks, etc. will likely get you blacklisted quickly).
;
;10.1.1.5 = example.com,example.net,example.org ; Messages from 10.1.1.5 may be relayed for example.com, example.net, and example.org.

[privs]
;relayin=1 ; Minimum privilege level required to accept external email for a user.
;relayout=1 ; Minimum privilege level required to relay external email outbound for a user.
Expand Down
23 changes: 23 additions & 0 deletions include/net_smtp.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ struct smtp_session;
/*! \brief Get the SMTP hostname of the local SMTP server, suitable for use in HELO/EHLO */
const char *smtp_hostname(void);

/*!
* \brief Whether an external host is allowed to relay mail for a particular domain
* \param srcip Client IP address
* \param hostname MAIL FROM domain
* \retval 1 if explicitly authorized
* \retval 0 if not authorized
*/
int smtp_relay_authorized(const char *srcip, const char *hostname);

/*!
* \brief Whether a message is exempt from certain checks due to it being accepted for relay from another MTA
* \param smtp
* \retval 1 if exempt
* \retval 0 if not exempt
*/
int smtp_is_exempt_relay(struct smtp_session *smtp);

/*!
* \brief Get a timestamp string appropriate for the Received header
* \param received Received time
Expand Down Expand Up @@ -107,6 +124,12 @@ struct bbs_node *smtp_node(struct smtp_session *smtp);
/*! \brief Get SMTP protocol used */
const char *smtp_protname(struct smtp_session *smtp);

/*! \brief Get the SMTP MAIL FROM address */
const char *smtp_from(struct smtp_session *smtp);

/*! \brief Get the SMTP MAIL FROM domain */
const char *smtp_from_domain(struct smtp_session *smtp);

/*! \brief Whether SPF validation should be performed */
int smtp_should_validate_spf(struct smtp_session *smtp);

Expand Down
31 changes: 22 additions & 9 deletions modules/mod_smtp_delivery_external.c
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,8 @@ static int smtp_client_handshake(struct bbs_tcp_client *restrict client, const c
int res = 0;

smtp_client_send(client, "EHLO %s\r\n", smtp_hostname());
res = smtp_client_expect_final(client, MIN_MS(5), "250", STRLEN("250")); /* Won't return 250 if ESMTP not supported */
/* Don't use smtp_client_expect_final as we'll miss reading the capabilities */
res = bbs_tcp_client_expect(client, "\r\n", 1, MIN_MS(5), "250"); /* Won't return 250 if ESMTP not supported */
if (res) { /* Fall back to HELO if EHLO not supported */
if (require_starttls_out) { /* STARTTLS is only supported by EHLO, not HELO */
bbs_warning("SMTP server %s does not support STARTTLS, but encryption is mandatory. Delivery failed.\n", hostname);
Expand Down Expand Up @@ -871,14 +872,18 @@ static int external_delivery(struct smtp_session *smtp, struct smtp_response *re
return 0;
}

bbs_assert(fromlocal);
if (!accept_relay_out) {
smtp_abort(resp, 550, 5.7.0, "Mail relay denied.");
return -1;
} else if (fromlocal && minpriv_relay_out) {
if (smtp_node(smtp)->user->priv < minpriv_relay_out) {
smtp_abort(resp, 550, 5.7.0, "Mail relay denied. Unauthorized to relay external mail.");
if (smtp_is_exempt_relay(smtp)) {
bbs_debug(2, "%s is explicitly authorized to relay mail from %s\n", smtp_node(smtp)->ip, smtp_from_domain(smtp));
} else {
bbs_assert(fromlocal); /* Shouldn't have slipped through to this point otherwise */
if (!accept_relay_out) {
smtp_abort(resp, 550, 5.7.0, "Mail relay denied.");
return -1;
} else if (fromlocal && minpriv_relay_out) {
if (smtp_node(smtp)->user->priv < minpriv_relay_out) {
smtp_abort(resp, 550, 5.7.0, "Mail relay denied. Unauthorized to relay external mail.");
return -1;
}
}
}

Expand Down Expand Up @@ -1057,6 +1062,12 @@ static int exists(struct smtp_session *smtp, struct smtp_response *resp, const c
UNUSED(user);
UNUSED(domain);

if (smtp_is_exempt_relay(smtp)) {
/* Allow an external host to relay messages for a domain if it's explicitly authorized to. */
bbs_debug(2, "%s is explicitly authorized to relay mail from %s\n", smtp_node(smtp)->ip, smtp_from_domain(smtp));
return 1;
}

if (!fromlocal) {/* External user trying to send us mail that's not for us. */
/* Built in rejection of relayed mail. If another delivery agent wants to override this, it can,
* (e.g. to set up a honeypot), it would just need to have a more urgent priority. */
Expand Down Expand Up @@ -1120,10 +1131,12 @@ static int load_module(void)

static int unload_module(void)
{
int res;
res = smtp_unregister_delivery_agent(&extdeliver);
bbs_pthread_cancel_kill(queue_thread);
bbs_pthread_join(queue_thread, NULL);
pthread_mutex_destroy(&queue_lock);
return smtp_unregister_delivery_agent(&extdeliver);
return res;
}

BBS_MODULE_INFO_DEPENDENT("E-Mail External Delivery", "net_smtp.so");
30 changes: 30 additions & 0 deletions modules/mod_smtp_filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ static int prepend_received(struct smtp_filter_data *f)
return 0;
}

/*! \brief Separate callback for adding Received header to relayed messages, since these could have multiple recipients */
static int relay_filter_cb(struct smtp_filter_data *f)
{
if (smtp_is_exempt_relay(f->smtp)) {
const char *prot;
char timestamp[40];
char hostname[256];

prot = smtp_protname(f->smtp);
smtp_timestamp(smtp_received_time(f->smtp), timestamp, sizeof(timestamp));
bbs_get_hostname(f->node->ip, hostname, sizeof(hostname)); /* Look up the sending IP */
/* The first hostname is the HELO/EHLO hostname.
* The second one is the reverse DNS hostname */
smtp_filter_write(f, "Received: from %s (%s [%s])\r\n\tby %s with %s\r\n\tfor %s; %s\r\n",
f->helohost, hostname, f->node->ip, bbs_hostname(), prot, f->recipient, timestamp); /* recipient already in <> */
}
return 0;
}

static int builtin_filter_cb(struct smtp_filter_data *f)
{
/* This is a good place to tack on Return-Path (receiving MTA does this) */
Expand All @@ -69,6 +88,11 @@ static int auth_filter_cb(struct smtp_filter_data *f)
#define HEADER_CONTINUE " "
char *buf;
int len;

if (smtp_is_exempt_relay(f->smtp)) {
return 0;
}

/* Add Authentication-Results header with the results of various tests */
len = asprintf(&buf, "%s" "%s%s%s%s" "%s%s" "%s%s" "%s%s",
bbs_hostname(),
Expand Down Expand Up @@ -96,13 +120,18 @@ struct smtp_filter_provider builtin_filter = {
.on_body = builtin_filter_cb,
};

struct smtp_filter_provider relay_filter = {
.on_body = relay_filter_cb,
};

struct smtp_filter_provider auth_filter = {
.on_body = auth_filter_cb,
};

static int load_module(void)
{
smtp_filter_register(&builtin_filter, SMTP_FILTER_PREPEND, SMTP_SCOPE_INDIVIDUAL, SMTP_DIRECTION_IN | SMTP_DIRECTION_SUBMIT, 1);
smtp_filter_register(&relay_filter, SMTP_FILTER_PREPEND, SMTP_SCOPE_COMBINED, SMTP_DIRECTION_OUT, 1); /* For messages that are being relayed */
/* Run this only after the SPF, DKIM, and DMARC filters have run: */
smtp_filter_register(&auth_filter, SMTP_FILTER_PREPEND, SMTP_SCOPE_COMBINED, SMTP_DIRECTION_IN, 5);
return 0;
Expand All @@ -111,6 +140,7 @@ static int load_module(void)
static int unload_module(void)
{
smtp_filter_unregister(&builtin_filter);
smtp_filter_unregister(&relay_filter);
smtp_filter_unregister(&auth_filter);
return 0;
}
Expand Down
5 changes: 5 additions & 0 deletions modules/mod_smtp_filter_arc.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ static int arc_filter_sign_cb(struct smtp_filter_data *f)
return 0;
}

if (smtp_is_exempt_relay(f->smtp)) {
bbs_debug(2, "Skipping ARC signing (%s explicitly authorized to relay mail from %s)\n", smtp_node(f->smtp)->ip, smtp_from_domain(f->smtp));
return 0;
}

/* We sign the message using the same domain that will be used in the outgoing MAIL FROM. */
domain = bbs_strcnext(f->from, '@');
if (!domain) {
Expand Down
4 changes: 4 additions & 0 deletions modules/mod_smtp_filter_dmarc.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ static int dmarc_filter_cb(struct smtp_filter_data *f)
const char *result = NULL;
int p = 0, sp = 0;

if (smtp_is_exempt_relay(f->smtp)) {
return 0;
}

is_ipv6 = !bbs_hostname_is_ipv4(f->node->ip); /* If it's not IPv4, must be IPv6? */
domain = bbs_strcnext(f->from, '@');
if (!domain) {
Expand Down
Loading

0 comments on commit aaf214c

Please sign in to comment.