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