aboutsummaryrefslogtreecommitdiffstats
path: root/smtpd/spfwalk.c
diff options
context:
space:
mode:
Diffstat (limited to 'smtpd/spfwalk.c')
-rw-r--r--smtpd/spfwalk.c391
1 files changed, 391 insertions, 0 deletions
diff --git a/smtpd/spfwalk.c b/smtpd/spfwalk.c
new file mode 100644
index 00000000..0832d1bc
--- /dev/null
+++ b/smtpd/spfwalk.c
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2017 Gilles Chehade <gilles@poolp.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/tree.h>
+
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include <asr.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "smtpd-defines.h"
+#include "smtpd-api.h"
+#include "unpack_dns.h"
+#include "parser.h"
+
+struct target {
+ void (*dispatch)(struct dns_rr *, struct target *);
+ int cidr4;
+ int cidr6;
+};
+
+int spfwalk(int, struct parameter *);
+
+static void dispatch_txt(struct dns_rr *, struct target *);
+static void dispatch_mx(struct dns_rr *, struct target *);
+static void dispatch_a(struct dns_rr *, struct target *);
+static void dispatch_aaaa(struct dns_rr *, struct target *);
+static void lookup_record(int, const char *, struct target *);
+static void dispatch_record(struct asr_result *, void *);
+static ssize_t parse_txt(const char *, size_t, char *, size_t);
+static int parse_target(char *, struct target *);
+void *xmalloc(size_t size);
+
+int ip_v4 = 0;
+int ip_v6 = 0;
+int ip_both = 1;
+
+struct dict seen;
+
+int
+spfwalk(int argc, struct parameter *argv)
+{
+ struct target tgt;
+ const char *ip_family = NULL;
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t linelen;
+
+ if (argv)
+ ip_family = argv[0].u.u_str;
+
+ if (ip_family) {
+ if (strcmp(ip_family, "-4") == 0) {
+ ip_both = 0;
+ ip_v4 = 1;
+ } else if (strcmp(ip_family, "-6") == 0) {
+ ip_both = 0;
+ ip_v6 = 1;
+ } else
+ errx(1, "invalid ip_family");
+ }
+
+ dict_init(&seen);
+ event_init();
+
+ tgt.cidr4 = tgt.cidr6 = -1;
+ tgt.dispatch = dispatch_txt;
+
+ while ((linelen = getline(&line, &linesize, stdin)) != -1) {
+ while (linelen-- > 0 && isspace((unsigned char)line[linelen]))
+ line[linelen] = '\0';
+
+ if (linelen > 0)
+ lookup_record(T_TXT, line, &tgt);
+ }
+
+ free(line);
+
+#if HAVE_PLEDGE
+ if (pledge("dns stdio", NULL) == -1)
+ err(1, "pledge");
+#endif
+
+ event_dispatch();
+
+ return 0;
+}
+
+void
+lookup_record(int type, const char *record, struct target *tgt)
+{
+ struct asr_query *as;
+ struct target *ntgt;
+
+ as = res_query_async(record, C_IN, type, NULL);
+ if (as == NULL)
+ err(1, "res_query_async");
+ ntgt = xmalloc(sizeof(*ntgt));
+ *ntgt = *tgt;
+ event_asr_run(as, dispatch_record, (void *)ntgt);
+}
+
+void
+dispatch_record(struct asr_result *ar, void *arg)
+{
+ struct target *tgt = arg;
+ struct unpack pack;
+ struct dns_header h;
+ struct dns_query q;
+ struct dns_rr rr;
+
+ /* best effort */
+ if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA)
+ goto end;
+
+ unpack_init(&pack, ar->ar_data, ar->ar_datalen);
+ unpack_header(&pack, &h);
+ unpack_query(&pack, &q);
+
+ for (; h.ancount; h.ancount--) {
+ unpack_rr(&pack, &rr);
+ /**/
+ tgt->dispatch(&rr, tgt);
+ }
+end:
+ free(tgt);
+}
+
+void
+dispatch_txt(struct dns_rr *rr, struct target *tgt)
+{
+ char buf[4096];
+ char *argv[512];
+ char buf2[512];
+ struct target ltgt;
+ struct in6_addr ina;
+ char **ap = argv;
+ char *in = buf;
+ char *record, *end;
+ ssize_t n;
+
+ if (rr->rr_type != T_TXT)
+ return;
+ n = parse_txt(rr->rr.other.rdata, rr->rr.other.rdlen, buf, sizeof(buf));
+ if (n == -1 || n == sizeof(buf))
+ return;
+ buf[n] = '\0';
+
+ if (strncasecmp("v=spf1 ", buf, 7))
+ return;
+
+ while ((*ap = strsep(&in, " ")) != NULL) {
+ if (strcasecmp(*ap, "v=spf1") == 0)
+ continue;
+
+ end = *ap + strlen(*ap)-1;
+ if (*end == '.')
+ *end = '\0';
+
+ if (dict_set(&seen, *ap, &seen))
+ continue;
+
+ if (**ap == '-' || **ap == '~')
+ continue;
+
+ if (**ap == '+' || **ap == '?')
+ (*ap)++;
+
+ ltgt.cidr4 = ltgt.cidr6 = -1;
+
+ if (strncasecmp("ip4:", *ap, 4) == 0) {
+ if ((ip_v4 == 1 || ip_both == 1) &&
+ inet_net_pton(AF_INET, *(ap) + 4,
+ &ina, sizeof(ina)) != -1)
+ printf("%s\n", *(ap) + 4);
+ continue;
+ }
+ if (strncasecmp("ip6:", *ap, 4) == 0) {
+ if ((ip_v6 == 1 || ip_both == 1) &&
+ inet_net_pton(AF_INET6, *(ap) + 4,
+ &ina, sizeof(ina)) != -1)
+ printf("%s\n", *(ap) + 4);
+ continue;
+ }
+ if (strcasecmp("a", *ap) == 0) {
+ print_dname(rr->rr_dname, buf2, sizeof(buf2));
+ buf2[strlen(buf2) - 1] = '\0';
+ ltgt.dispatch = dispatch_a;
+ lookup_record(T_A, buf2, &ltgt);
+ ltgt.dispatch = dispatch_aaaa;
+ lookup_record(T_AAAA, buf2, &ltgt);
+ continue;
+ }
+ if (strncasecmp("a:", *ap, 2) == 0) {
+ record = *(ap) + 2;
+ if (parse_target(record, &ltgt) < 0)
+ continue;
+ ltgt.dispatch = dispatch_a;
+ lookup_record(T_A, record, &ltgt);
+ ltgt.dispatch = dispatch_aaaa;
+ lookup_record(T_AAAA, record, &ltgt);
+ continue;
+ }
+ if (strncasecmp("exists:", *ap, 7) == 0) {
+ ltgt.dispatch = dispatch_a;
+ lookup_record(T_A, *(ap) + 7, &ltgt);
+ continue;
+ }
+ if (strncasecmp("include:", *ap, 8) == 0) {
+ ltgt.dispatch = dispatch_txt;
+ lookup_record(T_TXT, *(ap) + 8, &ltgt);
+ continue;
+ }
+ if (strncasecmp("redirect=", *ap, 9) == 0) {
+ ltgt.dispatch = dispatch_txt;
+ lookup_record(T_TXT, *(ap) + 9, &ltgt);
+ continue;
+ }
+ if (strcasecmp("mx", *ap) == 0) {
+ print_dname(rr->rr_dname, buf2, sizeof(buf2));
+ buf2[strlen(buf2) - 1] = '\0';
+ ltgt.dispatch = dispatch_mx;
+ lookup_record(T_MX, buf2, &ltgt);
+ continue;
+ }
+ if (strncasecmp("mx:", *ap, 3) == 0) {
+ record = *(ap) + 3;
+ if (parse_target(record, &ltgt) < 0)
+ continue;
+ ltgt.dispatch = dispatch_mx;
+ lookup_record(T_MX, record, &ltgt);
+ continue;
+ }
+ }
+ *ap = NULL;
+}
+
+void
+dispatch_mx(struct dns_rr *rr, struct target *tgt)
+{
+ char buf[512];
+ struct target ltgt;
+
+ if (rr->rr_type != T_MX)
+ return;
+
+ print_dname(rr->rr.mx.exchange, buf, sizeof(buf));
+ buf[strlen(buf) - 1] = '\0';
+ if (buf[strlen(buf) - 1] == '.')
+ buf[strlen(buf) - 1] = '\0';
+
+ ltgt = *tgt;
+ ltgt.dispatch = dispatch_a;
+ lookup_record(T_A, buf, &ltgt);
+ ltgt.dispatch = dispatch_aaaa;
+ lookup_record(T_AAAA, buf, &ltgt);
+}
+
+void
+dispatch_a(struct dns_rr *rr, struct target *tgt)
+{
+ char buffer[512];
+ const char *ptr;
+
+ if (rr->rr_type != T_A)
+ return;
+
+ if ((ptr = inet_ntop(AF_INET, &rr->rr.in_a.addr,
+ buffer, sizeof buffer))) {
+ if (tgt->cidr4 >= 0)
+ printf("%s/%d\n", ptr, tgt->cidr4);
+ else
+ printf("%s\n", ptr);
+ }
+}
+
+void
+dispatch_aaaa(struct dns_rr *rr, struct target *tgt)
+{
+ char buffer[512];
+ const char *ptr;
+
+ if (rr->rr_type != T_AAAA)
+ return;
+
+ if ((ptr = inet_ntop(AF_INET6, &rr->rr.in_aaaa.addr6,
+ buffer, sizeof buffer))) {
+ if (tgt->cidr6 >= 0)
+ printf("%s/%d\n", ptr, tgt->cidr6);
+ else
+ printf("%s\n", ptr);
+ }
+}
+
+ssize_t
+parse_txt(const char *rdata, size_t rdatalen, char *dst, size_t dstsz)
+{
+ size_t len;
+ ssize_t r = 0;
+
+ while (rdatalen) {
+ len = *(const unsigned char *)rdata;
+ if (len >= rdatalen) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ rdata++;
+ rdatalen--;
+
+ if (len == 0)
+ continue;
+
+ if (len >= dstsz) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+ memmove(dst, rdata, len);
+ dst += len;
+ dstsz -= len;
+
+ rdata += len;
+ rdatalen -= len;
+ r += len;
+ }
+
+ return r;
+}
+
+int
+parse_target(char *record, struct target *tgt)
+{
+ const char *err;
+ char *m4, *m6;
+
+ m4 = record;
+ strsep(&m4, "/");
+ if (m4 == NULL)
+ return 0;
+
+ m6 = m4;
+ strsep(&m6, "/");
+
+ if (*m4) {
+ tgt->cidr4 = strtonum(m4, 0, 32, &err);
+ if (err)
+ return tgt->cidr4 = -1;
+ }
+
+ if (m6 == NULL)
+ return 0;
+
+ tgt->cidr6 = strtonum(m6, 0, 128, &err);
+ if (err)
+ return tgt->cidr6 = -1;
+
+ return 0;
+}