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, <gt); 226 ltgt.dispatch = dispatch_aaaa; 227 lookup_record(T_AAAA, buf2, <gt); 228 continue; 229 } 230 if (strncasecmp("a:", *ap, 2) == 0) { 231 record = *(ap) + 2; 232 if (parse_target(record, <gt) < 0) 233 continue; 234 ltgt.dispatch = dispatch_a; 235 lookup_record(T_A, record, <gt); 236 ltgt.dispatch = dispatch_aaaa; 237 lookup_record(T_AAAA, record, <gt); 238 continue; 239 } 240 if (strncasecmp("exists:", *ap, 7) == 0) { 241 ltgt.dispatch = dispatch_a; 242 lookup_record(T_A, *(ap) + 7, <gt); 243 continue; 244 } 245 if (strncasecmp("include:", *ap, 8) == 0) { 246 ltgt.dispatch = dispatch_txt; 247 lookup_record(T_TXT, *(ap) + 8, <gt); 248 continue; 249 } 250 if (strncasecmp("redirect=", *ap, 9) == 0) { 251 ltgt.dispatch = dispatch_txt; 252 lookup_record(T_TXT, *(ap) + 9, <gt); 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, <gt); 260 continue; 261 } 262 if (strncasecmp("mx:", *ap, 3) == 0) { 263 record = *(ap) + 3; 264 if (parse_target(record, <gt) < 0) 265 continue; 266 ltgt.dispatch = dispatch_mx; 267 lookup_record(T_MX, record, <gt); 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, <gt); 291 ltgt.dispatch = dispatch_aaaa; 292 lookup_record(T_AAAA, buf, <gt); 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