summaryrefslogtreecommitdiff
path: root/usr.sbin
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin')
-rw-r--r--usr.sbin/smtpd/config.c3
-rw-r--r--usr.sbin/smtpd/lka_session.c27
-rw-r--r--usr.sbin/smtpd/mta.c9
-rw-r--r--usr.sbin/smtpd/mta_session.c24
-rw-r--r--usr.sbin/smtpd/parse.y43
-rw-r--r--usr.sbin/smtpd/smtpd.conf.520
-rw-r--r--usr.sbin/smtpd/smtpd.h14
-rw-r--r--usr.sbin/smtpd/smtpd/Makefile3
-rw-r--r--usr.sbin/smtpd/srs.c385
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;
+}