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