xref: /openbsd-src/usr.sbin/smtpd/spfwalk.c (revision d3140113bef2b86d3af61dd20c05a8630ff966c2)
14636661aSsunil /*
24636661aSsunil  * Copyright (c) 2017 Gilles Chehade <gilles@poolp.org>
34636661aSsunil  *
44636661aSsunil  * Permission to use, copy, modify, and distribute this software for any
54636661aSsunil  * purpose with or without fee is hereby granted, provided that the above
64636661aSsunil  * copyright notice and this permission notice appear in all copies.
74636661aSsunil  *
84636661aSsunil  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
94636661aSsunil  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
104636661aSsunil  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
114636661aSsunil  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
124636661aSsunil  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
134636661aSsunil  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
144636661aSsunil  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
154636661aSsunil  */
164636661aSsunil 
17f5d7b76bSsunil #include <sys/types.h>
184636661aSsunil #include <sys/socket.h>
1917d1c64aSeric #include <sys/tree.h>
204636661aSsunil 
21f5d7b76bSsunil #include <netinet/in.h>
224636661aSsunil 
23*d3140113Seric #include <arpa/inet.h>
244636661aSsunil #include <asr.h>
254636661aSsunil #include <ctype.h>
264636661aSsunil #include <err.h>
2750dbed5cSeric #include <errno.h>
284636661aSsunil #include <event.h>
29ccf50777Sgilles #include <limits.h>
30*d3140113Seric #include <netdb.h>
314636661aSsunil #include <stdio.h>
324636661aSsunil #include <stdlib.h>
334636661aSsunil #include <string.h>
344636661aSsunil #include <unistd.h>
354636661aSsunil 
364636661aSsunil #include "smtpd-defines.h"
3717d1c64aSeric #include "smtpd-api.h"
384636661aSsunil #include "unpack_dns.h"
394636661aSsunil #include "parser.h"
404636661aSsunil 
41ccf50777Sgilles struct target {
42ccf50777Sgilles 	void	(*dispatch)(struct dns_rr *, struct target *);
43ccf50777Sgilles 	int	  cidr4;
44ccf50777Sgilles 	int	  cidr6;
45ccf50777Sgilles };
46ccf50777Sgilles 
47aee31bfdSeric int spfwalk(int, struct parameter *);
484636661aSsunil 
49ccf50777Sgilles static void	dispatch_txt(struct dns_rr *, struct target *);
50ccf50777Sgilles static void	dispatch_mx(struct dns_rr *, struct target *);
51ccf50777Sgilles static void	dispatch_a(struct dns_rr *, struct target *);
52ccf50777Sgilles static void	dispatch_aaaa(struct dns_rr *, struct target *);
537c2d6922Smartijn static void	lookup_record(int, char *, struct target *);
544636661aSsunil static void	dispatch_record(struct asr_result *, void *);
5550dbed5cSeric static ssize_t	parse_txt(const char *, size_t, char *, size_t);
56ccf50777Sgilles static int	parse_target(char *, struct target *);
57ccf50777Sgilles void *xmalloc(size_t size);
584636661aSsunil 
594636661aSsunil int     ip_v4 = 0;
604636661aSsunil int     ip_v6 = 0;
614636661aSsunil int     ip_both = 1;
624636661aSsunil 
6317d1c64aSeric struct dict seen;
6417d1c64aSeric 
654636661aSsunil int
spfwalk(int argc,struct parameter * argv)66aee31bfdSeric spfwalk(int argc, struct parameter *argv)
674636661aSsunil {
68ccf50777Sgilles 	struct target	 tgt;
694636661aSsunil 	const char	*ip_family = NULL;
704636661aSsunil 	char		*line = NULL;
714636661aSsunil 	size_t		 linesize = 0;
724636661aSsunil 	ssize_t		 linelen;
734636661aSsunil 
744636661aSsunil 	if (argv)
754636661aSsunil 		ip_family = argv[0].u.u_str;
764636661aSsunil 
774636661aSsunil 	if (ip_family) {
784636661aSsunil 		if (strcmp(ip_family, "-4") == 0) {
794636661aSsunil 			ip_both = 0;
804636661aSsunil 			ip_v4 = 1;
814636661aSsunil 		} else if (strcmp(ip_family, "-6") == 0) {
824636661aSsunil 			ip_both = 0;
834636661aSsunil 			ip_v6 = 1;
844636661aSsunil 		} else
854636661aSsunil 			errx(1, "invalid ip_family");
864636661aSsunil 	}
874636661aSsunil 
8817d1c64aSeric 	dict_init(&seen);
894636661aSsunil   	event_init();
904636661aSsunil 
91ccf50777Sgilles 	tgt.cidr4 = tgt.cidr6 = -1;
92ccf50777Sgilles 	tgt.dispatch = dispatch_txt;
93ccf50777Sgilles 
944636661aSsunil 	while ((linelen = getline(&line, &linesize, stdin)) != -1) {
953d02c75dSmillert 		while (linelen-- > 0 && isspace((unsigned char)line[linelen]))
964636661aSsunil 			line[linelen] = '\0';
974636661aSsunil 
984636661aSsunil 		if (linelen > 0)
99ccf50777Sgilles 			lookup_record(T_TXT, line, &tgt);
1004636661aSsunil 	}
1014636661aSsunil 
1024636661aSsunil 	free(line);
1034636661aSsunil 
1044636661aSsunil 	if (pledge("dns stdio", NULL) == -1)
1054636661aSsunil 		err(1, "pledge");
1064636661aSsunil 
1074636661aSsunil   	event_dispatch();
1084636661aSsunil 
1094636661aSsunil 	return 0;
1104636661aSsunil }
1114636661aSsunil 
1124636661aSsunil void
lookup_record(int type,char * record,struct target * tgt)1137c2d6922Smartijn lookup_record(int type, char *record, struct target *tgt)
1144636661aSsunil {
1154636661aSsunil 	struct asr_query *as;
116ccf50777Sgilles 	struct target *ntgt;
11701f0d3baSmartijn 	size_t i;
1184636661aSsunil 
11901f0d3baSmartijn 	if (strchr(record, '%') != NULL) {
12001f0d3baSmartijn 		for (i = 0; record[i] != '\0'; i++) {
12101f0d3baSmartijn 			if (!isprint(record[i]))
12201f0d3baSmartijn 				record[i] = '?';
12301f0d3baSmartijn 		}
12401f0d3baSmartijn 		warnx("%s: %s contains macros and can't be resolved", __func__,
12501f0d3baSmartijn 		    record);
12601f0d3baSmartijn 		return;
12701f0d3baSmartijn 	}
1284636661aSsunil 	as = res_query_async(record, C_IN, type, NULL);
1294636661aSsunil 	if (as == NULL)
1304636661aSsunil 		err(1, "res_query_async");
131ccf50777Sgilles 	ntgt = xmalloc(sizeof(*ntgt));
132ccf50777Sgilles 	*ntgt = *tgt;
133ccf50777Sgilles 	event_asr_run(as, dispatch_record, (void *)ntgt);
1344636661aSsunil }
1354636661aSsunil 
1364636661aSsunil void
dispatch_record(struct asr_result * ar,void * arg)1374636661aSsunil dispatch_record(struct asr_result *ar, void *arg)
1384636661aSsunil {
139ccf50777Sgilles 	struct target *tgt = arg;
1404636661aSsunil 	struct unpack pack;
1414636661aSsunil 	struct dns_header h;
1424636661aSsunil 	struct dns_query q;
1434636661aSsunil 	struct dns_rr rr;
1444636661aSsunil 
1454636661aSsunil 	/* best effort */
1464636661aSsunil 	if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA)
147ccf50777Sgilles 		goto end;
1484636661aSsunil 
1494636661aSsunil 	unpack_init(&pack, ar->ar_data, ar->ar_datalen);
1504636661aSsunil 	unpack_header(&pack, &h);
1514636661aSsunil 	unpack_query(&pack, &q);
1524636661aSsunil 
1534636661aSsunil 	for (; h.ancount; h.ancount--) {
1544636661aSsunil 		unpack_rr(&pack, &rr);
1554636661aSsunil 		/**/
156ccf50777Sgilles 		tgt->dispatch(&rr, tgt);
1574636661aSsunil 	}
158ccf50777Sgilles end:
159ccf50777Sgilles 	free(tgt);
1604636661aSsunil }
1614636661aSsunil 
1624636661aSsunil void
dispatch_txt(struct dns_rr * rr,struct target * tgt)163ccf50777Sgilles dispatch_txt(struct dns_rr *rr, struct target *tgt)
1644636661aSsunil {
16550dbed5cSeric 	char buf[4096];
1664636661aSsunil 	char *argv[512];
167ccf50777Sgilles 	char buf2[512];
168ccf50777Sgilles 	struct target ltgt;
169ccf50777Sgilles 	struct in6_addr ina;
1704636661aSsunil 	char **ap = argv;
171ccf50777Sgilles 	char *in = buf;
172ccf50777Sgilles 	char *record, *end;
17350dbed5cSeric 	ssize_t n;
1744636661aSsunil 
1752d4b5909Sotto 	if (rr->rr_type != T_TXT)
1762d4b5909Sotto 		return;
17750dbed5cSeric 	n = parse_txt(rr->rr.other.rdata, rr->rr.other.rdlen, buf, sizeof(buf));
17850dbed5cSeric 	if (n == -1 || n == sizeof(buf))
17950dbed5cSeric 		return;
18050dbed5cSeric 	buf[n] = '\0';
18150dbed5cSeric 
1824636661aSsunil 	if (strncasecmp("v=spf1 ", buf, 7))
1834636661aSsunil 		return;
1844636661aSsunil 
1854636661aSsunil 	while ((*ap = strsep(&in, " ")) != NULL) {
1864636661aSsunil 		if (strcasecmp(*ap, "v=spf1") == 0)
1874636661aSsunil 			continue;
1884636661aSsunil 
1892c69f144Sgilles 		end = *ap + strlen(*ap)-1;
1902c69f144Sgilles 		if (*end == '.')
1912c69f144Sgilles 			*end = '\0';
1922c69f144Sgilles 
19317d1c64aSeric 		if (dict_set(&seen, *ap, &seen))
19417d1c64aSeric 			continue;
19517d1c64aSeric 
196b1cc6ff8Seric 		if (**ap == '-' || **ap == '~')
197b1cc6ff8Seric 			continue;
198b1cc6ff8Seric 
199b1cc6ff8Seric 		if (**ap == '+' || **ap == '?')
200b1cc6ff8Seric 			(*ap)++;
201b1cc6ff8Seric 
202ccf50777Sgilles 		ltgt.cidr4 = ltgt.cidr6 = -1;
203ccf50777Sgilles 
2044636661aSsunil 		if (strncasecmp("ip4:", *ap, 4) == 0) {
205a496a208Ssunil 			if ((ip_v4 == 1 || ip_both == 1) &&
206f5d7b76bSsunil 			    inet_net_pton(AF_INET, *(ap) + 4,
207f5d7b76bSsunil 			    &ina, sizeof(ina)) != -1)
2084636661aSsunil 				printf("%s\n", *(ap) + 4);
2094636661aSsunil 			continue;
2104636661aSsunil 		}
2114636661aSsunil 		if (strncasecmp("ip6:", *ap, 4) == 0) {
212a496a208Ssunil 			if ((ip_v6 == 1 || ip_both == 1) &&
213f5d7b76bSsunil 			    inet_net_pton(AF_INET6, *(ap) + 4,
214f5d7b76bSsunil 			    &ina, sizeof(ina)) != -1)
2154636661aSsunil 				printf("%s\n", *(ap) + 4);
2164636661aSsunil 			continue;
2174636661aSsunil 		}
21838073f33Sgilles 		if (strcasecmp("a", *ap) == 0) {
21938073f33Sgilles 			print_dname(rr->rr_dname, buf2, sizeof(buf2));
22038073f33Sgilles 			buf2[strlen(buf2) - 1] = '\0';
221ccf50777Sgilles 			ltgt.dispatch = dispatch_a;
222ccf50777Sgilles 			lookup_record(T_A, buf2, &ltgt);
223ccf50777Sgilles 			ltgt.dispatch = dispatch_aaaa;
224ccf50777Sgilles 			lookup_record(T_AAAA, buf2, &ltgt);
22538073f33Sgilles 			continue;
22638073f33Sgilles 		}
227dc2af26bSokan 		if (strncasecmp("a:", *ap, 2) == 0) {
228ccf50777Sgilles 			record = *(ap) + 2;
229ccf50777Sgilles 			if (parse_target(record, &ltgt) < 0)
230ccf50777Sgilles 				continue;
231ccf50777Sgilles 			ltgt.dispatch = dispatch_a;
232ccf50777Sgilles 			lookup_record(T_A, record, &ltgt);
233ccf50777Sgilles 			ltgt.dispatch = dispatch_aaaa;
234ccf50777Sgilles 			lookup_record(T_AAAA, record, &ltgt);
235dc2af26bSokan 			continue;
236dc2af26bSokan 		}
237dc2af26bSokan 		if (strncasecmp("exists:", *ap, 7) == 0) {
238ccf50777Sgilles 			ltgt.dispatch = dispatch_a;
239ccf50777Sgilles 			lookup_record(T_A, *(ap) + 7, &ltgt);
240dc2af26bSokan 			continue;
241dc2af26bSokan 		}
2424636661aSsunil 		if (strncasecmp("include:", *ap, 8) == 0) {
243ccf50777Sgilles 			ltgt.dispatch = dispatch_txt;
244ccf50777Sgilles 			lookup_record(T_TXT, *(ap) + 8, &ltgt);
2454636661aSsunil 			continue;
2464636661aSsunil 		}
2474636661aSsunil 		if (strncasecmp("redirect=", *ap, 9) == 0) {
248ccf50777Sgilles 			ltgt.dispatch = dispatch_txt;
249ccf50777Sgilles 			lookup_record(T_TXT, *(ap) + 9, &ltgt);
2504636661aSsunil 			continue;
2514636661aSsunil 		}
25238073f33Sgilles 		if (strcasecmp("mx", *ap) == 0) {
2534636661aSsunil 			print_dname(rr->rr_dname, buf2, sizeof(buf2));
2544636661aSsunil 			buf2[strlen(buf2) - 1] = '\0';
255ccf50777Sgilles 			ltgt.dispatch = dispatch_mx;
256ccf50777Sgilles 			lookup_record(T_MX, buf2, &ltgt);
2574636661aSsunil 			continue;
2584636661aSsunil 		}
2595e2c4cc0Sgilles 		if (strncasecmp("mx:", *ap, 3) == 0) {
260ccf50777Sgilles 			record = *(ap) + 3;
261ccf50777Sgilles 			if (parse_target(record, &ltgt) < 0)
262ccf50777Sgilles 				continue;
263ccf50777Sgilles 			ltgt.dispatch = dispatch_mx;
264ccf50777Sgilles 			lookup_record(T_MX, record, &ltgt);
2654636661aSsunil 			continue;
2664636661aSsunil 		}
2674636661aSsunil 	}
2684636661aSsunil 	*ap = NULL;
2694636661aSsunil }
2704636661aSsunil 
2714636661aSsunil void
dispatch_mx(struct dns_rr * rr,struct target * tgt)272ccf50777Sgilles dispatch_mx(struct dns_rr *rr, struct target *tgt)
2734636661aSsunil {
2744636661aSsunil 	char buf[512];
275ccf50777Sgilles 	struct target ltgt;
2764636661aSsunil 
277cc302d38Seric 	if (rr->rr_type != T_MX)
278cc302d38Seric 		return;
279cc302d38Seric 
2804636661aSsunil 	print_dname(rr->rr.mx.exchange, buf, sizeof(buf));
2814636661aSsunil 	buf[strlen(buf) - 1] = '\0';
2824636661aSsunil 	if (buf[strlen(buf) - 1] == '.')
2834636661aSsunil 		buf[strlen(buf) - 1] = '\0';
284ccf50777Sgilles 
285ccf50777Sgilles 	ltgt = *tgt;
286ccf50777Sgilles 	ltgt.dispatch = dispatch_a;
287ccf50777Sgilles 	lookup_record(T_A, buf, &ltgt);
288ccf50777Sgilles 	ltgt.dispatch = dispatch_aaaa;
289ccf50777Sgilles 	lookup_record(T_AAAA, buf, &ltgt);
2904636661aSsunil }
2914636661aSsunil 
2924636661aSsunil void
dispatch_a(struct dns_rr * rr,struct target * tgt)293ccf50777Sgilles dispatch_a(struct dns_rr *rr, struct target *tgt)
2944636661aSsunil {
2954636661aSsunil 	char buffer[512];
2964636661aSsunil 	const char *ptr;
2974636661aSsunil 
298cc302d38Seric 	if (rr->rr_type != T_A)
299cc302d38Seric 		return;
300cc302d38Seric 
3014636661aSsunil 	if ((ptr = inet_ntop(AF_INET, &rr->rr.in_a.addr,
302ccf50777Sgilles 	    buffer, sizeof buffer))) {
303ccf50777Sgilles 		if (tgt->cidr4 >= 0)
304ccf50777Sgilles 			printf("%s/%d\n", ptr, tgt->cidr4);
305ccf50777Sgilles 		else
3064636661aSsunil 			printf("%s\n", ptr);
3074636661aSsunil 	}
308ccf50777Sgilles }
3094636661aSsunil 
3104636661aSsunil void
dispatch_aaaa(struct dns_rr * rr,struct target * tgt)311ccf50777Sgilles dispatch_aaaa(struct dns_rr *rr, struct target *tgt)
3124636661aSsunil {
3134636661aSsunil 	char buffer[512];
3144636661aSsunil 	const char *ptr;
3154636661aSsunil 
316cc302d38Seric 	if (rr->rr_type != T_AAAA)
317cc302d38Seric 		return;
318cc302d38Seric 
3194636661aSsunil 	if ((ptr = inet_ntop(AF_INET6, &rr->rr.in_aaaa.addr6,
320ccf50777Sgilles 	    buffer, sizeof buffer))) {
321ccf50777Sgilles 		if (tgt->cidr6 >= 0)
322ccf50777Sgilles 			printf("%s/%d\n", ptr, tgt->cidr6);
323ccf50777Sgilles 		else
3244636661aSsunil 			printf("%s\n", ptr);
3254636661aSsunil 	}
326ccf50777Sgilles }
32750dbed5cSeric 
328ccf50777Sgilles ssize_t
parse_txt(const char * rdata,size_t rdatalen,char * dst,size_t dstsz)32950dbed5cSeric parse_txt(const char *rdata, size_t rdatalen, char *dst, size_t dstsz)
33050dbed5cSeric {
33150dbed5cSeric 	size_t len;
33250dbed5cSeric 	ssize_t r = 0;
33350dbed5cSeric 
33450dbed5cSeric 	while (rdatalen) {
33550dbed5cSeric 		len = *(const unsigned char *)rdata;
33650dbed5cSeric 		if (len >= rdatalen) {
33750dbed5cSeric 			errno = EINVAL;
33850dbed5cSeric 			return -1;
33950dbed5cSeric 		}
34050dbed5cSeric 
34150dbed5cSeric 		rdata++;
34250dbed5cSeric 		rdatalen--;
34350dbed5cSeric 
34450dbed5cSeric 		if (len == 0)
34550dbed5cSeric 			continue;
34650dbed5cSeric 
34750dbed5cSeric 		if (len >= dstsz) {
34850dbed5cSeric 			errno = EOVERFLOW;
34950dbed5cSeric 			return -1;
35050dbed5cSeric 		}
35150dbed5cSeric 		memmove(dst, rdata, len);
35250dbed5cSeric 		dst += len;
35350dbed5cSeric 		dstsz -= len;
35450dbed5cSeric 
35550dbed5cSeric 		rdata += len;
35650dbed5cSeric 		rdatalen -= len;
35750dbed5cSeric 		r += len;
35850dbed5cSeric 	}
35950dbed5cSeric 
36050dbed5cSeric 	return r;
36150dbed5cSeric }
362ccf50777Sgilles 
363ccf50777Sgilles int
parse_target(char * record,struct target * tgt)364ccf50777Sgilles parse_target(char *record, struct target *tgt)
365ccf50777Sgilles {
366ccf50777Sgilles 	const char *err;
367ccf50777Sgilles 	char *m4, *m6;
368ccf50777Sgilles 
369ccf50777Sgilles 	m4 = record;
370ccf50777Sgilles 	strsep(&m4, "/");
371ccf50777Sgilles 	if (m4 == NULL)
372ccf50777Sgilles 		return 0;
373ccf50777Sgilles 
374ccf50777Sgilles 	m6 = m4;
375ccf50777Sgilles 	strsep(&m6, "/");
376ccf50777Sgilles 
377ccf50777Sgilles 	if (*m4) {
378ccf50777Sgilles 		tgt->cidr4 = strtonum(m4, 0, 32, &err);
379ccf50777Sgilles 		if (err)
380ccf50777Sgilles 			return tgt->cidr4 = -1;
381ccf50777Sgilles 	}
382ccf50777Sgilles 
383ccf50777Sgilles 	if (m6 == NULL)
384ccf50777Sgilles 		return 0;
385ccf50777Sgilles 
386ccf50777Sgilles 	tgt->cidr6 = strtonum(m6, 0, 128, &err);
387ccf50777Sgilles 	if (err)
388ccf50777Sgilles 		return tgt->cidr6 = -1;
389ccf50777Sgilles 
390ccf50777Sgilles 	return 0;
391ccf50777Sgilles }
392