diff options
Diffstat (limited to 'srs.c')
-rw-r--r-- | srs.c | 377 |
1 files changed, 0 insertions, 377 deletions
@@ -1,377 +0,0 @@ -/* $OpenBSD: srs.c,v 1.3 2019/09/29 10:03:49 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_rfc3548(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; - int ret; - - /* 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_rfc3548(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; - int ret; - - /* 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_rfc3548(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_rfc3548(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_rfc3548(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_rfc3548(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_rfc3548(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; -} |