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
41struct target {
42	void	(*dispatch)(struct dns_rr *, struct target *);
43	int	  cidr4;
44	int	  cidr6;
45};
46
47int spfwalk(int, struct parameter *);
48
49static void	dispatch_txt(struct dns_rr *, struct target *);
50static void	dispatch_mx(struct dns_rr *, struct target *);
51static void	dispatch_a(struct dns_rr *, struct target *);
52static void	dispatch_aaaa(struct dns_rr *, struct target *);
53static void	lookup_record(int, char *, struct target *);
54static void	dispatch_record(struct asr_result *, void *);
55static ssize_t	parse_txt(const char *, size_t, char *, size_t);
56static int	parse_target(char *, struct target *);
57void *xmalloc(size_t size);
58
59int     ip_v4 = 0;
60int     ip_v6 = 0;
61int     ip_both = 1;
62
63struct dict seen;
64
65int
66spfwalk(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
112void
113lookup_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
136void
137dispatch_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	}
158end:
159	free(tgt);
160}
161
162void
163dispatch_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
271void
272dispatch_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
292void
293dispatch_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
310void
311dispatch_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
328ssize_t
329parse_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
363int
364parse_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