diff options
-rw-r--r-- | usr.sbin/smtpd/config.c | 3 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka_session.c | 27 | ||||
-rw-r--r-- | usr.sbin/smtpd/mta.c | 9 | ||||
-rw-r--r-- | usr.sbin/smtpd/mta_session.c | 24 | ||||
-rw-r--r-- | usr.sbin/smtpd/parse.y | 43 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.conf.5 | 20 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h | 14 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd/Makefile | 3 | ||||
-rw-r--r-- | usr.sbin/smtpd/srs.c | 385 |
9 files changed, 515 insertions, 13 deletions
diff --git a/usr.sbin/smtpd/config.c b/usr.sbin/smtpd/config.c index 8b8b857096e..cc53272ab6d 100644 --- a/usr.sbin/smtpd/config.c +++ b/usr.sbin/smtpd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.49 2018/12/28 14:21:02 eric Exp $ */ +/* $OpenBSD: config.c,v 1.50 2019/09/20 17:46:05 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -60,6 +60,7 @@ config_default(void) conf->sc_maxsize = DEFAULT_MAX_BODY_SIZE; conf->sc_subaddressing_delim = SUBADDRESSING_DELIMITER; conf->sc_ttl = SMTPD_QUEUE_EXPIRY; + conf->sc_srs_ttl = SMTPD_QUEUE_EXPIRY / 86400; conf->sc_mta_max_deferred = 100; conf->sc_scheduler_max_inflight = 5000; diff --git a/usr.sbin/smtpd/lka_session.c b/usr.sbin/smtpd/lka_session.c index ed17adcbb6b..ed1fd36fafd 100644 --- a/usr.sbin/smtpd/lka_session.c +++ b/usr.sbin/smtpd/lka_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka_session.c,v 1.92 2018/12/28 11:40:29 eric Exp $ */ +/* $OpenBSD: lka_session.c,v 1.93 2019/09/20 17:46:05 gilles Exp $ */ /* * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> @@ -267,7 +267,8 @@ lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn) int r; union lookup lk; char *tag; - + const char *srs_decoded; + if (xn->depth >= EXPAND_DEPTH) { log_trace(TRACE_EXPAND, "expand: lka_expand: node too deep."); lks->error = LKA_PERMFAIL; @@ -287,12 +288,32 @@ lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn) "[depth=%d]", xn->u.mailaddr.user, xn->u.mailaddr.domain, xn->depth); - /* Pass the node through the ruleset */ + ep = lks->envelope; ep.dest = xn->u.mailaddr; if (xn->parent) /* nodes with parent are forward addresses */ ep.flags |= EF_INTERNAL; + /* handle SRS */ + if (env->sc_srs_key != NULL && + ep.sender.user[0] == '\0' && + (strncasecmp(ep.rcpt.user, "SRS0=", 5) == 0 || + strncasecmp(ep.rcpt.user, "SRS1=", 5) == 0)) { + srs_decoded = srs_decode(mailaddr_to_text(&ep.rcpt)); + if (srs_decoded && + text_to_mailaddr(&ep.rcpt, srs_decoded)) { + /* flag envelope internal and override rcpt */ + ep.flags |= EF_INTERNAL; + xn->u.mailaddr = ep.rcpt; + lks->envelope = ep; + } + else { + log_warn("SRS failed to decode: %s", + mailaddr_to_text(&ep.rcpt)); + } + } + + /* Pass the node through the ruleset */ rule = ruleset_match(&ep); if (rule == NULL || rule->reject) { lks->error = (errno == EAGAIN) ? diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c index 926b089a5f5..6c817d00919 100644 --- a/usr.sbin/smtpd/mta.c +++ b/usr.sbin/smtpd/mta.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mta.c,v 1.231 2019/09/18 11:26:30 eric Exp $ */ +/* $OpenBSD: mta.c,v 1.232 2019/09/20 17:46:05 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -1735,6 +1735,7 @@ mta_relay(struct envelope *e, struct relayhost *relayh) key.sourcetable = dispatcher->u.remote.source; key.helotable = dispatcher->u.remote.helo_source; key.heloname = dispatcher->u.remote.helo; + key.srs = dispatcher->u.remote.srs; if (relayh->hostname[0]) { key.domain = mta_domain(relayh->hostname, 1); @@ -1782,6 +1783,7 @@ mta_relay(struct envelope *e, struct relayhost *relayh) r->helotable = xstrdup(key.helotable); if (key.heloname) r->heloname = xstrdup(key.heloname); + r->srs = key.srs; SPLAY_INSERT(mta_relay_tree, &relays, r); stat_increment("mta.relay", 1); } else { @@ -2089,6 +2091,11 @@ mta_relay_cmp(const struct mta_relay *a, const struct mta_relay *b) if (a->backupname && ((r = strcmp(a->backupname, b->backupname)))) return (r); + if (a->srs < b->srs) + return (-1); + if (a->srs > b->srs) + return (1); + return (0); } diff --git a/usr.sbin/smtpd/mta_session.c b/usr.sbin/smtpd/mta_session.c index f5366623d3f..72969e0a897 100644 --- a/usr.sbin/smtpd/mta_session.c +++ b/usr.sbin/smtpd/mta_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mta_session.c,v 1.121 2019/09/18 11:26:30 eric Exp $ */ +/* $OpenBSD: mta_session.c,v 1.122 2019/09/20 17:46:05 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -529,8 +529,9 @@ mta_enter_state(struct mta_session *s, int newstate) char ibuf[LINE_MAX]; char obuf[LINE_MAX]; int offset; + const char *srs_sender; - again: +again: oldstate = s->state; log_trace(TRACE_MTA, "mta: %p: %s -> %s", s, @@ -728,6 +729,25 @@ mta_enter_state(struct mta_session *s, int newstate) s->hangon = 0; s->msgtried++; envid_sz = strlen(e->dsn_envid); + + /* SRS-encode if requested for the relay action, AND we're not + * bouncing, AND we have an RCPT which means we are forwarded, + * AND the RCPT has a '@' just for sanity check (will always). + */ + if (env->sc_srs_key != NULL && + s->relay->srs && + strchr(s->task->sender, '@') && + e->rcpt && + strchr(e->rcpt, '@')) { + /* encode and replace task sender with new SRS-sender */ + srs_sender = srs_encode(s->task->sender, + strchr(e->rcpt, '@') + 1); + if (srs_sender) { + free(s->task->sender); + s->task->sender = xstrdup(srs_sender); + } + } + if (s->ext & MTA_EXT_DSN) { mta_send(s, "MAIL FROM:<%s>%s%s%s%s", s->task->sender, diff --git a/usr.sbin/smtpd/parse.y b/usr.sbin/smtpd/parse.y index e52077aa6cd..4801d14f3f2 100644 --- a/usr.sbin/smtpd/parse.y +++ b/usr.sbin/smtpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.261 2019/09/06 08:23:56 martijn Exp $ */ +/* $OpenBSD: parse.y,v 1.262 2019/09/20 17:46:05 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -191,7 +191,7 @@ typedef struct { %token PHASE PKI PORT PROC PROC_EXEC PROXY_V2 %token QUEUE QUIT %token RCPT_TO RDNS RECIPIENT RECEIVEDAUTH REGEX RELAY REJECT REPORT REWRITE RSET -%token SCHEDULER SENDER SENDERS SMTP SMTP_IN SMTP_OUT SMTPS SOCKET SRC SUB_ADDR_DELIM +%token SCHEDULER SENDER SENDERS SMTP SMTP_IN SMTP_OUT SMTPS SOCKET SRC SRS SUB_ADDR_DELIM %token TABLE TAG TAGGED TLS TLS_REQUIRE TTL %token USER USERBASE %token VERIFY VIRTUAL @@ -217,6 +217,7 @@ grammar : /* empty */ | grammar queue '\n' | grammar scheduler '\n' | grammar smtp '\n' + | grammar srs '\n' | grammar listen '\n' | grammar table '\n' | grammar dispatcher '\n' @@ -537,6 +538,31 @@ SMTP LIMIT limits_smtp } ; +srs: +SRS KEY STRING { + conf->sc_srs_key = $3; +} +SRS KEY BACKUP STRING { + conf->sc_srs_key_backup = $3; +} +| SRS TTL STRING { + conf->sc_srs_ttl = delaytonum($3); + if (conf->sc_srs_ttl == -1) { + yyerror("ttl delay \"%s\" is invalid", $3); + free($3); + YYERROR; + } + + conf->sc_srs_ttl /= 86400; + if (conf->sc_srs_ttl == 0) { + yyerror("ttl delay \"%s\" is too short", $3); + free($3); + YYERROR; + } + free($3); +} +; + dispatcher_local_option: USER STRING { @@ -830,6 +856,18 @@ HELO STRING { dispatcher->u.remote.auth = strdup(t->t_name); } +| SRS { + if (conf->sc_srs_key == NULL) { + yyerror("an srs key is required for srs to be specified in an action"); + YYERROR; + } + if (dispatcher->u.remote.srs == 1) { + yyerror("srs already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.srs = 1; +} ; dispatcher_remote_options: @@ -2377,6 +2415,7 @@ lookup(char *s) { "smtps", SMTPS }, { "socket", SOCKET }, { "src", SRC }, + { "srs", SRS }, { "sub-addr-delim", SUB_ADDR_DELIM }, { "table", TABLE }, { "tag", TAG }, diff --git a/usr.sbin/smtpd/smtpd.conf.5 b/usr.sbin/smtpd/smtpd.conf.5 index 4de04c95018..1da4189c0ca 100644 --- a/usr.sbin/smtpd/smtpd.conf.5 +++ b/usr.sbin/smtpd/smtpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: smtpd.conf.5,v 1.224 2019/09/06 08:23:56 martijn Exp $ +.\" $OpenBSD: smtpd.conf.5,v 1.225 2019/09/20 17:46:05 gilles Exp $ .\" .\" Copyright (c) 2008 Janne Johansson <jj@openbsd.org> .\" Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> @@ -17,7 +17,7 @@ .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" -.Dd $Mdocdate: September 6 2019 $ +.Dd $Mdocdate: September 20 2019 $ .Dt SMTPD.CONF 5 .Os .Sh NAME @@ -270,6 +270,9 @@ and .Dq smtps protocols for authentication. Server certificates for those protocols are verified by default. +.It Cm srs +When relaying a mail resulting from a forward, +use the Sender Rewriting Scheme to rewrite sender address. .It Cm tls Op Cm no-verify Require TLS to be used when relaying, using mandatory STARTTLS by default. When used with a smarthost, the protocol must not be @@ -842,6 +845,19 @@ When resolving the local part of a local email address, ignore the ASCII and all characters following it. The default is .Ql + . +.It Ic srs Cm key Ar secret +Set the secret key to use for SRS, +the Sender Rewriting Scheme. +.It Ic srs Cm key backup Ar secret +Set a backup secret key to use as a fallback for SRS. +This can be used to implementation SRS key rotation. +.It Ic srs Cm ttl Ar delay +Set the time-to-live delay for SRS envelopes. +After this delay, +a bounce reply to the SRS address will be discarded to limit risks of forged addresses. +The default is four days +.Pq 4d . +The delay .It Ic table Ar name Oo Ar type : Oc Ns Ar pathname Tables provide additional configuration information for .Xr smtpd 8 diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index e5a968e9a5e..7ba6a472a19 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.638 2019/09/19 07:35:36 gilles Exp $ */ +/* $OpenBSD: smtpd.h,v 1.639 2019/09/20 17:46:05 gilles Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -618,6 +618,10 @@ struct smtpd { char *sc_tls_ciphers; char *sc_subaddressing_delim; + + char *sc_srs_key; + char *sc_srs_key_backup; + int sc_srs_ttl; }; #define TRACE_DEBUG 0x0001 @@ -804,6 +808,7 @@ struct mta_relay { char *helotable; char *heloname; char *secret; + int srs; int state; size_t ntask; @@ -1162,6 +1167,8 @@ struct dispatcher_remote { int backup; char *backupmx; + + int srs; }; struct dispatcher_bounce { @@ -1587,6 +1594,11 @@ void log_imsg(int, int, struct imsg *); int fork_proc_backend(const char *, const char *, const char *); +/* srs.c */ +const char *srs_encode(const char *, const char *); +const char *srs_decode(const char *); + + /* ssl_smtpd.c */ void *ssl_mta_init(void *, char *, off_t, const char *); void *ssl_smtp_init(void *, int); diff --git a/usr.sbin/smtpd/smtpd/Makefile b/usr.sbin/smtpd/smtpd/Makefile index 46e80c6a5ea..985c812e448 100644 --- a/usr.sbin/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.103 2019/09/18 11:26:30 eric Exp $ +# $OpenBSD: Makefile,v 1.104 2019/09/20 17:46:05 gilles Exp $ .PATH: ${.CURDIR}/.. @@ -50,6 +50,7 @@ SRCS+= scheduler_backend.c SRCS+= smtp.c SRCS+= smtp_session.c SRCS+= smtpd.c +SRCS+= srs.c SRCS+= ssl.c SRCS+= ssl_smtpd.c SRCS+= ssl_verify.c diff --git a/usr.sbin/smtpd/srs.c b/usr.sbin/smtpd/srs.c new file mode 100644 index 00000000000..555d03a629d --- /dev/null +++ b/usr.sbin/smtpd/srs.c @@ -0,0 +1,385 @@ +/* $OpenBSD: srs.c,v 1.1 2019/09/20 17:46:05 gilles Exp $ */ + +/* + * Copyright (c) 2019 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <inttypes.h> +#include <netdb.h> +#include <limits.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <openssl/sha.h> + +#include "smtpd.h" +#include "log.h" + +static uint8_t base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +static int +minrange(uint16_t tref, uint16_t t2, int drift, int mod) +{ + if (tref > drift) { + /* t2 must fall in between tref and tref - drift */ + if (t2 <= tref && t2>= tref - drift) + return 1; + } + else { + /* t2 must fall in between 0 and tref, or wrap */ + if (t2 <= tref || t2 >= mod - (drift - tref)) + return 1; + } + return 0; +} + +static int +maxrange(uint16_t tref, uint16_t t2, int drift, int mod) +{ + if (tref + drift < 1024) { + /* t2 must fall in between tref and tref + drift */ + if (t2 >= tref && t2 <= tref + drift) + return 1; + } + else { + /* t2 must fall in between tref + drift, or wrap */ + if (t2 >= tref || t2 <= (tref + drift) % 1024) + return 1; + } + return 0; +} + +static int +timestamp_check_range(uint16_t tref, uint16_t t2) +{ + if (! minrange(tref, t2, env->sc_srs_ttl, 1024) && + ! maxrange(tref, t2, 1, 1024)) + return 0; + + return 1; +} + +static const unsigned char * +srs_hash(const char *key, const char *value) +{ + SHA_CTX c; + static unsigned char md[SHA_DIGEST_LENGTH]; + + SHA1_Init(&c); + SHA1_Update(&c, key, strlen(key)); + SHA1_Update(&c, value, strlen(value)); + SHA1_Final(md, &c); + return md; +} + +static const char * +srs0_encode(const char *sender, const char *rcpt_domain) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + uint16_t timestamp; + int ret; + + /* compute 10 bits timestamp according to spec */ + timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; + + /* parse sender into user and domain */ + if (! text_to_mailaddr(&maddr, sender)) + return sender; + + /* TT=<orig_domainpart>=<orig_userpart>@<new_domainpart> */ + ret = snprintf(tmp, sizeof tmp, "%c%c=%s=%s@%s", + base32[(timestamp>>5) & 0x1F], + base32[timestamp & 0x1F], + maddr.domain, maddr.user, rcpt_domain); + if (ret == -1 || ret >= (int)sizeof tmp) + return sender; + + /* compute HHHH */ + base64_encode(srs_hash(env->sc_srs_key, tmp), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* prepend SRS0=HHHH= prefix */ + ret = snprintf(dest, sizeof dest, "SRS0=%c%c%c%c=%s", + md[0], md[1], md[2], md[3], tmp); + if (ret == -1 || ret >= (int)sizeof dest) + return sender; + + return dest; +} + +static const char * +srs1_encode_srs0(const char *sender, const char *rcpt_domain) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + uint16_t timestamp; + int ret; + + /* compute 10 bits timestamp according to spec */ + timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; + + /* parse sender into user and domain */ + if (! text_to_mailaddr(&maddr, sender)) + return sender; + + /* <last_domainpart>==<SRS0_userpart>@<new_domainpart> */ + ret = snprintf(tmp, sizeof tmp, "%s==%s@%s", + maddr.domain, maddr.user, rcpt_domain); + if (ret == -1 || ret >= (int)sizeof tmp) + return sender; + + /* compute HHHH */ + base64_encode(srs_hash(env->sc_srs_key, tmp), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* prepend SRS1=HHHH= prefix */ + ret = snprintf(dest, sizeof dest, "SRS1=%c%c%c%c=%s", + md[0], md[1], md[2], md[3], tmp); + if (ret == -1 || ret >= (int)sizeof dest) + return sender; + + return dest; +} + +static const char * +srs1_encode_srs1(const char *sender, const char *rcpt_domain) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + uint16_t timestamp; + int ret; + + /* compute 10 bits timestamp according to spec */ + timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; + + /* parse sender into user and domain */ + if (! text_to_mailaddr(&maddr, sender)) + return sender; + + /* <SRS1_userpart>@<new_domainpart> */ + ret = snprintf(tmp, sizeof tmp, "%s@%s", maddr.user, rcpt_domain); + if (ret == -1 || ret >= (int)sizeof tmp) + return sender; + + /* sanity check: there's at least room for a checksum + * with allowed delimiter =, + or - + */ + if (strlen(tmp) < 5) + return sender; + if (tmp[4] != '=' && tmp[4] != '+' && tmp[4] != '-') + return sender; + + /* compute HHHH */ + base64_encode(srs_hash(env->sc_srs_key, tmp + 5), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* prepend SRS1=HHHH= prefix skipping previous hops' HHHH */ + ret = snprintf(dest, sizeof dest, "SRS1=%c%c%c%c=%s", + md[0], md[1], md[2], md[3], tmp + 5); + if (ret == -1 || ret >= (int)sizeof dest) + return sender; + + return dest; +} + +const char * +srs_encode(const char *sender, const char *rcpt_domain) +{ + if (strncasecmp(sender, "SRS0=", 5) == 0) + return srs1_encode_srs0(sender+5, rcpt_domain); + if (strncasecmp(sender, "SRS1=", 5) == 0) + return srs1_encode_srs1(sender+5, rcpt_domain); + return srs0_encode(sender, rcpt_domain); +} + +static const char * +srs0_decode(const char *rcpt) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + char *p; + uint8_t *idx; + int ret; + uint16_t timestamp, srs_timestamp; + + /* sanity check: we have room for a checksum and delimiter */ + if (strlen(rcpt) < 5) + return NULL; + + /* compute checksum */ + base64_encode(srs_hash(env->sc_srs_key, rcpt+5), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* compare prefix checksum with computed checksum */ + if (strncmp(md, rcpt, 4) != 0) { + if (env->sc_srs_key_backup == NULL) + return NULL; + base64_encode(srs_hash(env->sc_srs_key_backup, rcpt+5), + SHA_DIGEST_LENGTH, md, sizeof md); + if (strncmp(md, rcpt, 4) != 0) + return NULL; + } + rcpt += 5; + + /* sanity check: we have room for a timestamp and delimiter */ + if (strlen(rcpt) < 3) + return NULL; + + /* decode timestamp */ + if ((idx = strchr(base32, rcpt[0])) == NULL) + return NULL; + srs_timestamp = ((idx - base32) << 5); + + if ((idx = strchr(base32, rcpt[1])) == NULL) + return NULL; + srs_timestamp |= (idx - base32); + rcpt += 3; + + /* compute current 10 bits timestamp */ + timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; + + /* check that SRS timestamp isn't too far from current */ + if (timestamp != srs_timestamp) + if (! timestamp_check_range(timestamp, srs_timestamp)) + return NULL; + + if (! text_to_mailaddr(&maddr, rcpt)) + return NULL; + + /* sanity check: we have at least one SRS separator */ + if ((p = strchr(maddr.user, '=')) == NULL) + return NULL; + *p++ = '\0'; + + /* maddr.user holds "domain\0user", with p pointing at user */ + ret = snprintf(dest, sizeof dest, "%s@%s", p, maddr.user); + if (ret == -1 || ret >= (int)sizeof dest) + return NULL; + + return dest; +} + +static const char * +srs1_decode(const char *rcpt) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + char *p; + uint8_t *idx; + int ret; + uint16_t timestamp, srs_timestamp; + + /* sanity check: we have room for a checksum and delimiter */ + if (strlen(rcpt) < 5) + return NULL; + + /* compute checksum */ + base64_encode(srs_hash(env->sc_srs_key, rcpt+5), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* compare prefix checksum with computed checksum */ + if (strncmp(md, rcpt, 4) != 0) { + if (env->sc_srs_key_backup == NULL) + return NULL; + base64_encode(srs_hash(env->sc_srs_key_backup, rcpt+5), + SHA_DIGEST_LENGTH, md, sizeof md); + if (strncmp(md, rcpt, 4) != 0) + return NULL; + } + rcpt += 5; + + if (! text_to_mailaddr(&maddr, rcpt)) + return NULL; + + /* sanity check: we have at least one SRS separator */ + if ((p = strchr(maddr.user, '=')) == NULL) + return NULL; + *p++ = '\0'; + + /* maddr.user holds "domain\0user", with p pointing at user */ + ret = snprintf(dest, sizeof dest, "SRS0%s@%s", p, maddr.user); + if (ret == -1 || ret >= (int)sizeof dest) + return NULL; + + + /* we're ready to return decoded address, but let's check if + * SRS0 timestamp is valid. + */ + + /* first, get rid of SRS0 checksum (=HHHH=), we can't check it */ + if (strlen(p) < 6) + return NULL; + p += 6; + + /* we should be pointing to a timestamp, check that we're indeed */ + if (strlen(p) < 3) + return NULL; + if (p[2] != '=' && p[2] != '+' && p[2] != '-') + return NULL; + p[2] = '\0'; + + if ((idx = strchr(base32, p[0])) == NULL) + return NULL; + srs_timestamp = ((idx - base32) << 5); + + if ((idx = strchr(base32, p[1])) == NULL) + return NULL; + srs_timestamp |= (idx - base32); + + /* compute current 10 bits timestamp */ + timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; + + /* check that SRS timestamp isn't too far from current */ + if (timestamp != srs_timestamp) + if (! timestamp_check_range(timestamp, srs_timestamp)) + return NULL; + + return dest; +} + +const char * +srs_decode(const char *rcpt) +{ + if (strncasecmp(rcpt, "SRS0=", 5) == 0) + return srs0_decode(rcpt + 5); + if (strncasecmp(rcpt, "SRS1=", 5) == 0) + return srs1_decode(rcpt + 5); + + return NULL; +} |