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