xref: /openbsd/usr.sbin/smtpd/spfwalk.c (revision 905646f0)
1 /*
2  * Copyright (c) 2017 Gilles Chehade <gilles@poolp.org>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include <sys/types.h>
18 #include <sys/socket.h>
19 #include <sys/tree.h>
20 
21 #include <arpa/inet.h>
22 #include <arpa/nameser.h>
23 #include <netinet/in.h>
24 #include <netdb.h>
25 
26 #include <asr.h>
27 #include <ctype.h>
28 #include <err.h>
29 #include <errno.h>
30 #include <event.h>
31 #include <imsg.h>
32 #include <limits.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <strings.h>
37 #include <unistd.h>
38 
39 #include "smtpd-defines.h"
40 #include "smtpd-api.h"
41 #include "unpack_dns.h"
42 #include "parser.h"
43 
44 struct target {
45 	void	(*dispatch)(struct dns_rr *, struct target *);
46 	int	  cidr4;
47 	int	  cidr6;
48 };
49 
50 int spfwalk(int, struct parameter *);
51 
52 static void	dispatch_txt(struct dns_rr *, struct target *);
53 static void	dispatch_mx(struct dns_rr *, struct target *);
54 static void	dispatch_a(struct dns_rr *, struct target *);
55 static void	dispatch_aaaa(struct dns_rr *, struct target *);
56 static void	lookup_record(int, char *, struct target *);
57 static void	dispatch_record(struct asr_result *, void *);
58 static ssize_t	parse_txt(const char *, size_t, char *, size_t);
59 static int	parse_target(char *, struct target *);
60 void *xmalloc(size_t size);
61 
62 int     ip_v4 = 0;
63 int     ip_v6 = 0;
64 int     ip_both = 1;
65 
66 struct dict seen;
67 
68 int
69 spfwalk(int argc, struct parameter *argv)
70 {
71 	struct target	 tgt;
72 	const char	*ip_family = NULL;
73 	char		*line = NULL;
74 	size_t		 linesize = 0;
75 	ssize_t		 linelen;
76 
77 	if (argv)
78 		ip_family = argv[0].u.u_str;
79 
80 	if (ip_family) {
81 		if (strcmp(ip_family, "-4") == 0) {
82 			ip_both = 0;
83 			ip_v4 = 1;
84 		} else if (strcmp(ip_family, "-6") == 0) {
85 			ip_both = 0;
86 			ip_v6 = 1;
87 		} else
88 			errx(1, "invalid ip_family");
89 	}
90 
91 	dict_init(&seen);
92   	event_init();
93 
94 	tgt.cidr4 = tgt.cidr6 = -1;
95 	tgt.dispatch = dispatch_txt;
96 
97 	while ((linelen = getline(&line, &linesize, stdin)) != -1) {
98 		while (linelen-- > 0 && isspace((unsigned char)line[linelen]))
99 			line[linelen] = '\0';
100 
101 		if (linelen > 0)
102 			lookup_record(T_TXT, line, &tgt);
103 	}
104 
105 	free(line);
106 
107 	if (pledge("dns stdio", NULL) == -1)
108 		err(1, "pledge");
109 
110   	event_dispatch();
111 
112 	return 0;
113 }
114 
115 void
116 lookup_record(int type, char *record, struct target *tgt)
117 {
118 	struct asr_query *as;
119 	struct target *ntgt;
120 	size_t i;
121 
122 	if (strchr(record, '%') != NULL) {
123 		for (i = 0; record[i] != '\0'; i++) {
124 			if (!isprint(record[i]))
125 				record[i] = '?';
126 		}
127 		warnx("%s: %s contains macros and can't be resolved", __func__,
128 		    record);
129 		return;
130 	}
131 	as = res_query_async(record, C_IN, type, NULL);
132 	if (as == NULL)
133 		err(1, "res_query_async");
134 	ntgt = xmalloc(sizeof(*ntgt));
135 	*ntgt = *tgt;
136 	event_asr_run(as, dispatch_record, (void *)ntgt);
137 }
138 
139 void
140 dispatch_record(struct asr_result *ar, void *arg)
141 {
142 	struct target *tgt = arg;
143 	struct unpack pack;
144 	struct dns_header h;
145 	struct dns_query q;
146 	struct dns_rr rr;
147 
148 	/* best effort */
149 	if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA)
150 		goto end;
151 
152 	unpack_init(&pack, ar->ar_data, ar->ar_datalen);
153 	unpack_header(&pack, &h);
154 	unpack_query(&pack, &q);
155 
156 	for (; h.ancount; h.ancount--) {
157 		unpack_rr(&pack, &rr);
158 		/**/
159 		tgt->dispatch(&rr, tgt);
160 	}
161 end:
162 	free(tgt);
163 }
164 
165 void
166 dispatch_txt(struct dns_rr *rr, struct target *tgt)
167 {
168 	char buf[4096];
169 	char *argv[512];
170 	char buf2[512];
171 	struct target ltgt;
172 	struct in6_addr ina;
173 	char **ap = argv;
174 	char *in = buf;
175 	char *record, *end;
176 	ssize_t n;
177 
178 	if (rr->rr_type != T_TXT)
179 		return;
180 	n = parse_txt(rr->rr.other.rdata, rr->rr.other.rdlen, buf, sizeof(buf));
181 	if (n == -1 || n == sizeof(buf))
182 		return;
183 	buf[n] = '\0';
184 
185 	if (strncasecmp("v=spf1 ", buf, 7))
186 		return;
187 
188 	while ((*ap = strsep(&in, " ")) != NULL) {
189 		if (strcasecmp(*ap, "v=spf1") == 0)
190 			continue;
191 
192 		end = *ap + strlen(*ap)-1;
193 		if (*end == '.')
194 			*end = '\0';
195 
196 		if (dict_set(&seen, *ap, &seen))
197 			continue;
198 
199 		if (**ap == '-' || **ap == '~')
200 			continue;
201 
202 		if (**ap == '+' || **ap == '?')
203 			(*ap)++;
204 
205 		ltgt.cidr4 = ltgt.cidr6 = -1;
206 
207 		if (strncasecmp("ip4:", *ap, 4) == 0) {
208 			if ((ip_v4 == 1 || ip_both == 1) &&
209 			    inet_net_pton(AF_INET, *(ap) + 4,
210 			    &ina, sizeof(ina)) != -1)
211 				printf("%s\n", *(ap) + 4);
212 			continue;
213 		}
214 		if (strncasecmp("ip6:", *ap, 4) == 0) {
215 			if ((ip_v6 == 1 || ip_both == 1) &&
216 			    inet_net_pton(AF_INET6, *(ap) + 4,
217 			    &ina, sizeof(ina)) != -1)
218 				printf("%s\n", *(ap) + 4);
219 			continue;
220 		}
221 		if (strcasecmp("a", *ap) == 0) {
222 			print_dname(rr->rr_dname, buf2, sizeof(buf2));
223 			buf2[strlen(buf2) - 1] = '\0';
224 			ltgt.dispatch = dispatch_a;
225 			lookup_record(T_A, buf2, &ltgt);
226 			ltgt.dispatch = dispatch_aaaa;
227 			lookup_record(T_AAAA, buf2, &ltgt);
228 			continue;
229 		}
230 		if (strncasecmp("a:", *ap, 2) == 0) {
231 			record = *(ap) + 2;
232 			if (parse_target(record, &ltgt) < 0)
233 				continue;
234 			ltgt.dispatch = dispatch_a;
235 			lookup_record(T_A, record, &ltgt);
236 			ltgt.dispatch = dispatch_aaaa;
237 			lookup_record(T_AAAA, record, &ltgt);
238 			continue;
239 		}
240 		if (strncasecmp("exists:", *ap, 7) == 0) {
241 			ltgt.dispatch = dispatch_a;
242 			lookup_record(T_A, *(ap) + 7, &ltgt);
243 			continue;
244 		}
245 		if (strncasecmp("include:", *ap, 8) == 0) {
246 			ltgt.dispatch = dispatch_txt;
247 			lookup_record(T_TXT, *(ap) + 8, &ltgt);
248 			continue;
249 		}
250 		if (strncasecmp("redirect=", *ap, 9) == 0) {
251 			ltgt.dispatch = dispatch_txt;
252 			lookup_record(T_TXT, *(ap) + 9, &ltgt);
253 			continue;
254 		}
255 		if (strcasecmp("mx", *ap) == 0) {
256 			print_dname(rr->rr_dname, buf2, sizeof(buf2));
257 			buf2[strlen(buf2) - 1] = '\0';
258 			ltgt.dispatch = dispatch_mx;
259 			lookup_record(T_MX, buf2, &ltgt);
260 			continue;
261 		}
262 		if (strncasecmp("mx:", *ap, 3) == 0) {
263 			record = *(ap) + 3;
264 			if (parse_target(record, &ltgt) < 0)
265 				continue;
266 			ltgt.dispatch = dispatch_mx;
267 			lookup_record(T_MX, record, &ltgt);
268 			continue;
269 		}
270 	}
271 	*ap = NULL;
272 }
273 
274 void
275 dispatch_mx(struct dns_rr *rr, struct target *tgt)
276 {
277 	char buf[512];
278 	struct target ltgt;
279 
280 	if (rr->rr_type != T_MX)
281 		return;
282 
283 	print_dname(rr->rr.mx.exchange, buf, sizeof(buf));
284 	buf[strlen(buf) - 1] = '\0';
285 	if (buf[strlen(buf) - 1] == '.')
286 		buf[strlen(buf) - 1] = '\0';
287 
288 	ltgt = *tgt;
289 	ltgt.dispatch = dispatch_a;
290 	lookup_record(T_A, buf, &ltgt);
291 	ltgt.dispatch = dispatch_aaaa;
292 	lookup_record(T_AAAA, buf, &ltgt);
293 }
294 
295 void
296 dispatch_a(struct dns_rr *rr, struct target *tgt)
297 {
298 	char buffer[512];
299 	const char *ptr;
300 
301 	if (rr->rr_type != T_A)
302 		return;
303 
304 	if ((ptr = inet_ntop(AF_INET, &rr->rr.in_a.addr,
305 	    buffer, sizeof buffer))) {
306 		if (tgt->cidr4 >= 0)
307 			printf("%s/%d\n", ptr, tgt->cidr4);
308 		else
309 			printf("%s\n", ptr);
310 	}
311 }
312 
313 void
314 dispatch_aaaa(struct dns_rr *rr, struct target *tgt)
315 {
316 	char buffer[512];
317 	const char *ptr;
318 
319 	if (rr->rr_type != T_AAAA)
320 		return;
321 
322 	if ((ptr = inet_ntop(AF_INET6, &rr->rr.in_aaaa.addr6,
323 	    buffer, sizeof buffer))) {
324 		if (tgt->cidr6 >= 0)
325 			printf("%s/%d\n", ptr, tgt->cidr6);
326 		else
327 			printf("%s\n", ptr);
328 	}
329 }
330 
331 ssize_t
332 parse_txt(const char *rdata, size_t rdatalen, char *dst, size_t dstsz)
333 {
334 	size_t len;
335 	ssize_t r = 0;
336 
337 	while (rdatalen) {
338 		len = *(const unsigned char *)rdata;
339 		if (len >= rdatalen) {
340 			errno = EINVAL;
341 			return -1;
342 		}
343 
344 		rdata++;
345 		rdatalen--;
346 
347 		if (len == 0)
348 			continue;
349 
350 		if (len >= dstsz) {
351 			errno = EOVERFLOW;
352 			return -1;
353 		}
354 		memmove(dst, rdata, len);
355 		dst += len;
356 		dstsz -= len;
357 
358 		rdata += len;
359 		rdatalen -= len;
360 		r += len;
361 	}
362 
363 	return r;
364 }
365 
366 int
367 parse_target(char *record, struct target *tgt)
368 {
369 	const char *err;
370 	char *m4, *m6;
371 
372 	m4 = record;
373 	strsep(&m4, "/");
374 	if (m4 == NULL)
375 		return 0;
376 
377 	m6 = m4;
378 	strsep(&m6, "/");
379 
380 	if (*m4) {
381 		tgt->cidr4 = strtonum(m4, 0, 32, &err);
382 		if (err)
383 			return tgt->cidr4 = -1;
384 	}
385 
386 	if (m6 == NULL)
387 		return 0;
388 
389 	tgt->cidr6 = strtonum(m6, 0, 128, &err);
390 	if (err)
391 		return tgt->cidr6 = -1;
392 
393 	return 0;
394 }
395