aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGilles Chehade <gilles@poolp.org>2019-09-20 20:04:24 +0200
committerGilles Chehade <gilles@poolp.org>2019-09-20 20:04:24 +0200
commit220873c9e0ba080127c91481079807cc56694bdb (patch)
tree0bc7229e9bd699cc6ad1c50aa7fa05a43cfc0573
parentsync (diff)
downloadOpenSMTPD-220873c9e0ba080127c91481079807cc56694bdb.tar.xz
OpenSMTPD-220873c9e0ba080127c91481079807cc56694bdb.zip
works better when srs.c is committed
-rw-r--r--smtpd/srs.c387
1 files changed, 387 insertions, 0 deletions
diff --git a/smtpd/srs.c b/smtpd/srs.c
new file mode 100644
index 00000000..9d0daade
--- /dev/null
+++ b/smtpd/srs.c
@@ -0,0 +1,387 @@
+/* $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 "includes.h"
+
+#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;
+}