File: [local] / src / usr.sbin / smtpd / spfwalk.c (download)
Revision 1.20, Mon Jun 14 17:58:16 2021 UTC (2 years, 11 months ago) by eric
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, HEAD Changes since 1.19: +2 -5 lines
add required headers for smtpd.h and remove unnecessary ones in other files.
ok jung@
|
/*
* 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 <sys/types.h>
#include <sys/socket.h>
#include <sys/tree.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <asr.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <event.h>
#include <limits.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.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, 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 (pledge("dns stdio", NULL) == -1)
err(1, "pledge");
event_dispatch();
return 0;
}
void
lookup_record(int type, char *record, struct target *tgt)
{
struct asr_query *as;
struct target *ntgt;
size_t i;
if (strchr(record, '%') != NULL) {
for (i = 0; record[i] != '\0'; i++) {
if (!isprint(record[i]))
record[i] = '?';
}
warnx("%s: %s contains macros and can't be resolved", __func__,
record);
return;
}
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, <gt);
ltgt.dispatch = dispatch_aaaa;
lookup_record(T_AAAA, buf2, <gt);
continue;
}
if (strncasecmp("a:", *ap, 2) == 0) {
record = *(ap) + 2;
if (parse_target(record, <gt) < 0)
continue;
ltgt.dispatch = dispatch_a;
lookup_record(T_A, record, <gt);
ltgt.dispatch = dispatch_aaaa;
lookup_record(T_AAAA, record, <gt);
continue;
}
if (strncasecmp("exists:", *ap, 7) == 0) {
ltgt.dispatch = dispatch_a;
lookup_record(T_A, *(ap) + 7, <gt);
continue;
}
if (strncasecmp("include:", *ap, 8) == 0) {
ltgt.dispatch = dispatch_txt;
lookup_record(T_TXT, *(ap) + 8, <gt);
continue;
}
if (strncasecmp("redirect=", *ap, 9) == 0) {
ltgt.dispatch = dispatch_txt;
lookup_record(T_TXT, *(ap) + 9, <gt);
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, <gt);
continue;
}
if (strncasecmp("mx:", *ap, 3) == 0) {
record = *(ap) + 3;
if (parse_target(record, <gt) < 0)
continue;
ltgt.dispatch = dispatch_mx;
lookup_record(T_MX, record, <gt);
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, <gt);
ltgt.dispatch = dispatch_aaaa;
lookup_record(T_AAAA, buf, <gt);
}
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;
}