1 /* $OpenBSD: srs.c,v 1.5 2021/06/14 17:58:16 eric Exp $ */ 2 3 /* 4 * Copyright (c) 2019 Gilles Chehade <gilles@poolp.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <openssl/sha.h> 20 #include <string.h> 21 22 #include "smtpd.h" 23 24 static uint8_t base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; 25 26 static int 27 minrange(uint16_t tref, uint16_t t2, int drift, int mod) 28 { 29 if (tref > drift) { 30 /* t2 must fall in between tref and tref - drift */ 31 if (t2 <= tref && t2>= tref - drift) 32 return 1; 33 } 34 else { 35 /* t2 must fall in between 0 and tref, or wrap */ 36 if (t2 <= tref || t2 >= mod - (drift - tref)) 37 return 1; 38 } 39 return 0; 40 } 41 42 static int 43 maxrange(uint16_t tref, uint16_t t2, int drift, int mod) 44 { 45 if (tref + drift < 1024) { 46 /* t2 must fall in between tref and tref + drift */ 47 if (t2 >= tref && t2 <= tref + drift) 48 return 1; 49 } 50 else { 51 /* t2 must fall in between tref + drift, or wrap */ 52 if (t2 >= tref || t2 <= (tref + drift) % 1024) 53 return 1; 54 } 55 return 0; 56 } 57 58 static int 59 timestamp_check_range(uint16_t tref, uint16_t t2) 60 { 61 if (! minrange(tref, t2, env->sc_srs_ttl, 1024) && 62 ! maxrange(tref, t2, 1, 1024)) 63 return 0; 64 65 return 1; 66 } 67 68 static const unsigned char * 69 srs_hash(const char *key, const char *value) 70 { 71 SHA_CTX c; 72 static unsigned char md[SHA_DIGEST_LENGTH]; 73 74 SHA1_Init(&c); 75 SHA1_Update(&c, key, strlen(key)); 76 SHA1_Update(&c, value, strlen(value)); 77 SHA1_Final(md, &c); 78 return md; 79 } 80 81 static const char * 82 srs0_encode(const char *sender, const char *rcpt_domain) 83 { 84 static char dest[SMTPD_MAXMAILADDRSIZE]; 85 char tmp[SMTPD_MAXMAILADDRSIZE]; 86 char md[SHA_DIGEST_LENGTH*4+1]; 87 struct mailaddr maddr; 88 uint16_t timestamp; 89 int ret; 90 91 /* compute 10 bits timestamp according to spec */ 92 timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; 93 94 /* parse sender into user and domain */ 95 if (! text_to_mailaddr(&maddr, sender)) 96 return sender; 97 98 /* TT=<orig_domainpart>=<orig_userpart>@<new_domainpart> */ 99 ret = snprintf(tmp, sizeof tmp, "%c%c=%s=%s@%s", 100 base32[(timestamp>>5) & 0x1F], 101 base32[timestamp & 0x1F], 102 maddr.domain, maddr.user, rcpt_domain); 103 if (ret == -1 || ret >= (int)sizeof tmp) 104 return sender; 105 106 /* compute HHHH */ 107 base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp), SHA_DIGEST_LENGTH, 108 md, sizeof md); 109 110 /* prepend SRS0=HHHH= prefix */ 111 ret = snprintf(dest, sizeof dest, "SRS0=%c%c%c%c=%s", 112 md[0], md[1], md[2], md[3], tmp); 113 if (ret == -1 || ret >= (int)sizeof dest) 114 return sender; 115 116 return dest; 117 } 118 119 static const char * 120 srs1_encode_srs0(const char *sender, const char *rcpt_domain) 121 { 122 static char dest[SMTPD_MAXMAILADDRSIZE]; 123 char tmp[SMTPD_MAXMAILADDRSIZE]; 124 char md[SHA_DIGEST_LENGTH*4+1]; 125 struct mailaddr maddr; 126 int ret; 127 128 /* parse sender into user and domain */ 129 if (! text_to_mailaddr(&maddr, sender)) 130 return sender; 131 132 /* <last_domainpart>==<SRS0_userpart>@<new_domainpart> */ 133 ret = snprintf(tmp, sizeof tmp, "%s==%s@%s", 134 maddr.domain, maddr.user, rcpt_domain); 135 if (ret == -1 || ret >= (int)sizeof tmp) 136 return sender; 137 138 /* compute HHHH */ 139 base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp), SHA_DIGEST_LENGTH, 140 md, sizeof md); 141 142 /* prepend SRS1=HHHH= prefix */ 143 ret = snprintf(dest, sizeof dest, "SRS1=%c%c%c%c=%s", 144 md[0], md[1], md[2], md[3], tmp); 145 if (ret == -1 || ret >= (int)sizeof dest) 146 return sender; 147 148 return dest; 149 } 150 151 static const char * 152 srs1_encode_srs1(const char *sender, const char *rcpt_domain) 153 { 154 static char dest[SMTPD_MAXMAILADDRSIZE]; 155 char tmp[SMTPD_MAXMAILADDRSIZE]; 156 char md[SHA_DIGEST_LENGTH*4+1]; 157 struct mailaddr maddr; 158 int ret; 159 160 /* parse sender into user and domain */ 161 if (! text_to_mailaddr(&maddr, sender)) 162 return sender; 163 164 /* <SRS1_userpart>@<new_domainpart> */ 165 ret = snprintf(tmp, sizeof tmp, "%s@%s", maddr.user, rcpt_domain); 166 if (ret == -1 || ret >= (int)sizeof tmp) 167 return sender; 168 169 /* sanity check: there's at least room for a checksum 170 * with allowed delimiter =, + or - 171 */ 172 if (strlen(tmp) < 5) 173 return sender; 174 if (tmp[4] != '=' && tmp[4] != '+' && tmp[4] != '-') 175 return sender; 176 177 /* compute HHHH */ 178 base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp + 5), SHA_DIGEST_LENGTH, 179 md, sizeof md); 180 181 /* prepend SRS1=HHHH= prefix skipping previous hops' HHHH */ 182 ret = snprintf(dest, sizeof dest, "SRS1=%c%c%c%c=%s", 183 md[0], md[1], md[2], md[3], tmp + 5); 184 if (ret == -1 || ret >= (int)sizeof dest) 185 return sender; 186 187 return dest; 188 } 189 190 const char * 191 srs_encode(const char *sender, const char *rcpt_domain) 192 { 193 if (strncasecmp(sender, "SRS0=", 5) == 0) 194 return srs1_encode_srs0(sender+5, rcpt_domain); 195 if (strncasecmp(sender, "SRS1=", 5) == 0) 196 return srs1_encode_srs1(sender+5, rcpt_domain); 197 return srs0_encode(sender, rcpt_domain); 198 } 199 200 static const char * 201 srs0_decode(const char *rcpt) 202 { 203 static char dest[SMTPD_MAXMAILADDRSIZE]; 204 char md[SHA_DIGEST_LENGTH*4+1]; 205 struct mailaddr maddr; 206 char *p; 207 uint8_t *idx; 208 int ret; 209 uint16_t timestamp, srs_timestamp; 210 211 /* sanity check: we have room for a checksum and delimiter */ 212 if (strlen(rcpt) < 5) 213 return NULL; 214 215 /* compute checksum */ 216 base64_encode_rfc3548(srs_hash(env->sc_srs_key, rcpt+5), SHA_DIGEST_LENGTH, 217 md, sizeof md); 218 219 /* compare prefix checksum with computed checksum */ 220 if (strncmp(md, rcpt, 4) != 0) { 221 if (env->sc_srs_key_backup == NULL) 222 return NULL; 223 base64_encode_rfc3548(srs_hash(env->sc_srs_key_backup, rcpt+5), 224 SHA_DIGEST_LENGTH, md, sizeof md); 225 if (strncmp(md, rcpt, 4) != 0) 226 return NULL; 227 } 228 rcpt += 5; 229 230 /* sanity check: we have room for a timestamp and delimiter */ 231 if (strlen(rcpt) < 3) 232 return NULL; 233 234 /* decode timestamp */ 235 if ((idx = strchr(base32, rcpt[0])) == NULL) 236 return NULL; 237 srs_timestamp = ((idx - base32) << 5); 238 239 if ((idx = strchr(base32, rcpt[1])) == NULL) 240 return NULL; 241 srs_timestamp |= (idx - base32); 242 rcpt += 3; 243 244 /* compute current 10 bits timestamp */ 245 timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; 246 247 /* check that SRS timestamp isn't too far from current */ 248 if (timestamp != srs_timestamp) 249 if (! timestamp_check_range(timestamp, srs_timestamp)) 250 return NULL; 251 252 if (! text_to_mailaddr(&maddr, rcpt)) 253 return NULL; 254 255 /* sanity check: we have at least one SRS separator */ 256 if ((p = strchr(maddr.user, '=')) == NULL) 257 return NULL; 258 *p++ = '\0'; 259 260 /* maddr.user holds "domain\0user", with p pointing at user */ 261 ret = snprintf(dest, sizeof dest, "%s@%s", p, maddr.user); 262 if (ret == -1 || ret >= (int)sizeof dest) 263 return NULL; 264 265 return dest; 266 } 267 268 static const char * 269 srs1_decode(const char *rcpt) 270 { 271 static char dest[SMTPD_MAXMAILADDRSIZE]; 272 char md[SHA_DIGEST_LENGTH*4+1]; 273 struct mailaddr maddr; 274 char *p; 275 uint8_t *idx; 276 int ret; 277 uint16_t timestamp, srs_timestamp; 278 279 /* sanity check: we have room for a checksum and delimiter */ 280 if (strlen(rcpt) < 5) 281 return NULL; 282 283 /* compute checksum */ 284 base64_encode_rfc3548(srs_hash(env->sc_srs_key, rcpt+5), SHA_DIGEST_LENGTH, 285 md, sizeof md); 286 287 /* compare prefix checksum with computed checksum */ 288 if (strncmp(md, rcpt, 4) != 0) { 289 if (env->sc_srs_key_backup == NULL) 290 return NULL; 291 base64_encode_rfc3548(srs_hash(env->sc_srs_key_backup, rcpt+5), 292 SHA_DIGEST_LENGTH, md, sizeof md); 293 if (strncmp(md, rcpt, 4) != 0) 294 return NULL; 295 } 296 rcpt += 5; 297 298 if (! text_to_mailaddr(&maddr, rcpt)) 299 return NULL; 300 301 /* sanity check: we have at least one SRS separator */ 302 if ((p = strchr(maddr.user, '=')) == NULL) 303 return NULL; 304 *p++ = '\0'; 305 306 /* maddr.user holds "domain\0user", with p pointing at user */ 307 ret = snprintf(dest, sizeof dest, "SRS0%s@%s", p, maddr.user); 308 if (ret == -1 || ret >= (int)sizeof dest) 309 return NULL; 310 311 312 /* we're ready to return decoded address, but let's check if 313 * SRS0 timestamp is valid. 314 */ 315 316 /* first, get rid of SRS0 checksum (=HHHH=), we can't check it */ 317 if (strlen(p) < 6) 318 return NULL; 319 p += 6; 320 321 /* we should be pointing to a timestamp, check that we're indeed */ 322 if (strlen(p) < 3) 323 return NULL; 324 if (p[2] != '=' && p[2] != '+' && p[2] != '-') 325 return NULL; 326 p[2] = '\0'; 327 328 if ((idx = strchr(base32, p[0])) == NULL) 329 return NULL; 330 srs_timestamp = ((idx - base32) << 5); 331 332 if ((idx = strchr(base32, p[1])) == NULL) 333 return NULL; 334 srs_timestamp |= (idx - base32); 335 336 /* compute current 10 bits timestamp */ 337 timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; 338 339 /* check that SRS timestamp isn't too far from current */ 340 if (timestamp != srs_timestamp) 341 if (! timestamp_check_range(timestamp, srs_timestamp)) 342 return NULL; 343 344 return dest; 345 } 346 347 const char * 348 srs_decode(const char *rcpt) 349 { 350 if (strncasecmp(rcpt, "SRS0=", 5) == 0) 351 return srs0_decode(rcpt + 5); 352 if (strncasecmp(rcpt, "SRS1=", 5) == 0) 353 return srs1_decode(rcpt + 5); 354 355 return NULL; 356 } 357