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