xref: /openbsd/usr.sbin/smtpd/spfwalk.c (revision 274d7c50)
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 <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <strings.h>
35 #include <unistd.h>
36 
37 #define LINE_MAX 1024
38 #include "smtpd-defines.h"
39 #include "smtpd-api.h"
40 #include "unpack_dns.h"
41 #include "parser.h"
42 
43 int spfwalk(int, struct parameter *);
44 
45 static void	dispatch_txt(struct dns_rr *);
46 static void	dispatch_mx(struct dns_rr *);
47 static void	dispatch_a(struct dns_rr *);
48 static void	dispatch_aaaa(struct dns_rr *);
49 static void	lookup_record(int, const char *, void (*)(struct dns_rr *));
50 static void	dispatch_record(struct asr_result *, void *);
51 static ssize_t	parse_txt(const char *, size_t, char *, size_t);
52 
53 int     ip_v4 = 0;
54 int     ip_v6 = 0;
55 int     ip_both = 1;
56 
57 struct dict seen;
58 
59 int
60 spfwalk(int argc, struct parameter *argv)
61 {
62 	const char	*ip_family = NULL;
63 	char		*line = NULL;
64 	size_t		 linesize = 0;
65 	ssize_t		 linelen;
66 
67 	if (argv)
68 		ip_family = argv[0].u.u_str;
69 
70 	if (ip_family) {
71 		if (strcmp(ip_family, "-4") == 0) {
72 			ip_both = 0;
73 			ip_v4 = 1;
74 		} else if (strcmp(ip_family, "-6") == 0) {
75 			ip_both = 0;
76 			ip_v6 = 1;
77 		} else
78 			errx(1, "invalid ip_family");
79 	}
80 
81 	dict_init(&seen);
82   	event_init();
83 
84 	while ((linelen = getline(&line, &linesize, stdin)) != -1) {
85 		while (linelen-- > 0 && isspace(line[linelen]))
86 			line[linelen] = '\0';
87 
88 		if (linelen > 0)
89 			lookup_record(T_TXT, line, dispatch_txt);
90 	}
91 
92 	free(line);
93 
94 	if (pledge("dns stdio", NULL) == -1)
95 		err(1, "pledge");
96 
97   	event_dispatch();
98 
99 	return 0;
100 }
101 
102 void
103 lookup_record(int type, const char *record, void (*cb)(struct dns_rr *))
104 {
105 	struct asr_query *as;
106 
107 	as = res_query_async(record, C_IN, type, NULL);
108 	if (as == NULL)
109 		err(1, "res_query_async");
110 	event_asr_run(as, dispatch_record, cb);
111 }
112 
113 void
114 dispatch_record(struct asr_result *ar, void *arg)
115 {
116 	void (*cb)(struct dns_rr *) = arg;
117 	struct unpack pack;
118 	struct dns_header h;
119 	struct dns_query q;
120 	struct dns_rr rr;
121 
122 	/* best effort */
123 	if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA)
124 		return;
125 
126 	unpack_init(&pack, ar->ar_data, ar->ar_datalen);
127 	unpack_header(&pack, &h);
128 	unpack_query(&pack, &q);
129 
130 	for (; h.ancount; h.ancount--) {
131 		unpack_rr(&pack, &rr);
132 		/**/
133 		cb(&rr);
134 	}
135 }
136 
137 void
138 dispatch_txt(struct dns_rr *rr)
139 {
140 	struct in6_addr ina;
141         char buf[4096];
142         char buf2[512];
143         char *in = buf;
144         char *argv[512];
145         char **ap = argv;
146 	char *end;
147 	ssize_t n;
148 
149 	if (rr->rr_type != T_TXT)
150 		return;
151 	n = parse_txt(rr->rr.other.rdata, rr->rr.other.rdlen, buf, sizeof(buf));
152 	if (n == -1 || n == sizeof(buf))
153 		return;
154 	buf[n] = '\0';
155 
156 	if (strncasecmp("v=spf1 ", buf, 7))
157 		return;
158 
159 	while ((*ap = strsep(&in, " ")) != NULL) {
160 		if (strcasecmp(*ap, "v=spf1") == 0)
161 			continue;
162 
163 		end = *ap + strlen(*ap)-1;
164 		if (*end == '.')
165 			*end = '\0';
166 
167 		if (dict_set(&seen, *ap, &seen))
168 			continue;
169 
170 		if (**ap == '-' || **ap == '~')
171 			continue;
172 
173 		if (**ap == '+' || **ap == '?')
174 			(*ap)++;
175 
176 		if (strncasecmp("ip4:", *ap, 4) == 0) {
177 			if ((ip_v4 == 1 || ip_both == 1) &&
178 			    inet_net_pton(AF_INET, *(ap) + 4,
179 			    &ina, sizeof(ina)) != -1)
180 				printf("%s\n", *(ap) + 4);
181 			continue;
182 		}
183 		if (strncasecmp("ip6:", *ap, 4) == 0) {
184 			if ((ip_v6 == 1 || ip_both == 1) &&
185 			    inet_net_pton(AF_INET6, *(ap) + 4,
186 			    &ina, sizeof(ina)) != -1)
187 				printf("%s\n", *(ap) + 4);
188 			continue;
189 		}
190 		if (strcasecmp("a", *ap) == 0) {
191 			print_dname(rr->rr_dname, buf2, sizeof(buf2));
192 			buf2[strlen(buf2) - 1] = '\0';
193 			lookup_record(T_A, buf2, dispatch_a);
194 			lookup_record(T_AAAA, buf2, dispatch_aaaa);
195 			continue;
196 		}
197 		if (strncasecmp("a:", *ap, 2) == 0) {
198 			lookup_record(T_A, *(ap) + 2, dispatch_a);
199 			lookup_record(T_AAAA, *(ap) + 2, dispatch_aaaa);
200 			continue;
201 		}
202 		if (strncasecmp("exists:", *ap, 7) == 0) {
203 			lookup_record(T_A, *(ap) + 7, dispatch_a);
204 			continue;
205 		}
206 		if (strncasecmp("include:", *ap, 8) == 0) {
207 			lookup_record(T_TXT, *(ap) + 8, dispatch_txt);
208 			continue;
209 		}
210 		if (strncasecmp("redirect=", *ap, 9) == 0) {
211 			lookup_record(T_TXT, *(ap) + 9, dispatch_txt);
212 			continue;
213 		}
214 		if (strcasecmp("mx", *ap) == 0) {
215 			print_dname(rr->rr_dname, buf2, sizeof(buf2));
216 			buf2[strlen(buf2) - 1] = '\0';
217 			lookup_record(T_MX, buf2, dispatch_mx);
218 			continue;
219 		}
220 		if (strncasecmp("mx:", *ap, 3) == 0) {
221 			lookup_record(T_MX, *(ap) + 3, dispatch_mx);
222 			continue;
223 		}
224 	}
225 	*ap = NULL;
226 }
227 
228 void
229 dispatch_mx(struct dns_rr *rr)
230 {
231 	char buf[512];
232 
233 	if (rr->rr_type != T_MX)
234 		return;
235 
236 	print_dname(rr->rr.mx.exchange, buf, sizeof(buf));
237 	buf[strlen(buf) - 1] = '\0';
238 	if (buf[strlen(buf) - 1] == '.')
239 		buf[strlen(buf) - 1] = '\0';
240 	lookup_record(T_A, buf, dispatch_a);
241 	lookup_record(T_AAAA, buf, dispatch_aaaa);
242 }
243 
244 void
245 dispatch_a(struct dns_rr *rr)
246 {
247 	char buffer[512];
248 	const char *ptr;
249 
250 	if (rr->rr_type != T_A)
251 		return;
252 
253 	if ((ptr = inet_ntop(AF_INET, &rr->rr.in_a.addr,
254 	    buffer, sizeof buffer)))
255 		printf("%s\n", ptr);
256 }
257 
258 void
259 dispatch_aaaa(struct dns_rr *rr)
260 {
261 	char buffer[512];
262 	const char *ptr;
263 
264 	if (rr->rr_type != T_AAAA)
265 		return;
266 
267 	if ((ptr = inet_ntop(AF_INET6, &rr->rr.in_aaaa.addr6,
268 	    buffer, sizeof buffer)))
269 		printf("%s\n", ptr);
270 }
271 
272 static ssize_t
273 parse_txt(const char *rdata, size_t rdatalen, char *dst, size_t dstsz)
274 {
275 	size_t len;
276 	ssize_t r = 0;
277 
278 	while (rdatalen) {
279 		len = *(const unsigned char *)rdata;
280 		if (len >= rdatalen) {
281 			errno = EINVAL;
282 			return -1;
283 		}
284 
285 		rdata++;
286 		rdatalen--;
287 
288 		if (len == 0)
289 			continue;
290 
291 		if (len >= dstsz) {
292 			errno = EOVERFLOW;
293 			return -1;
294 		}
295 		memmove(dst, rdata, len);
296 		dst += len;
297 		dstsz -= len;
298 
299 		rdata += len;
300 		rdatalen -= len;
301 		r += len;
302 	}
303 
304 	return r;
305 }
306