On 2016-12-16 Clint Pachl <pa...@ecentryx.com> wrote: [...] > What would be > best is if we could blacklist these spammers upon first connection
I also wanted to just-in-time decisions, but with dnswl lookups. I wrote a program to intercept incoming, unknown smtp connections and do a dnswl lookup to whitelist them just in time. You could do the same for blacklisting, but only for lookups based on ip because the program looks only at the initial syn packet. For me this helped a lot to deliver mails faster which would otherwise be delayed in the greytrap, or even get stuck, because they come from smtp pools. here are the pf rules: pass in on egress inet proto tcp to (self) port smtp flags S/SA no state divert-packet port 25 pass in on egress inet proto tcp from <dnswl-grey> to (self) port smtp keep state rdr-to 127.0.0.1 port spamd pass in log (to pflog1) on egress proto tcp from {<spamd-white> <dnswl-white>} to port smtp keep state and here's the C program. It still has lots of dead debugging code.: #include <sys/types.h> #include <sys/time.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/fcntl.h> #include <sys/wait.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip6.h> #include <netinet/tcp.h> #include <net/if.h> #include <net/pfvar.h> #include <arpa/inet.h> #include <arpa/nameser.h> #include <resolv.h> #include <poll.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <time.h> #include <pwd.h> #include <grp.h> #include <err.h> #include <assert.h> #define DEBUG 0 #define DIVERT_PORT 25 #define NSTATES 10 struct dns_header { uint16_t id; uint16_t flags; #define QR 0x8000 #define OPCODE_MASK 0x7800 #define OPCODE_SHIFT 11 #define AA 0x0400 #define TC 0x0200 #define RD 0x0100 #define RA 0x0080 #define AD 0x0020 #define CD 0x0010 #define RCODE_MASK 0x000f #define RCODE_SHIFT 0 uint16_t qdcount; uint16_t ancount; uint16_t nscount; uint16_t arcount; }; struct dns_record { uint16_t type; uint16_t class; uint32_t ttl; uint16_t length; }; struct state { union { struct in_addr in4; struct in6_addr in6; uint8_t octets[sizeof(struct in6_addr)]; } addr; struct timespec timeout; int af; uint16_t dnskey; } states[NSTATES]; void send_query(struct state *state, const char *question); void process_response(); void enlist(struct state *state, int white); int dnssock, pfdev; const char *const whitelists[] = { "list.dnswl.org", "swl.spamhaus.org", }; int main(int argc, char *argv[]) { int i, ret; time_t t; struct sockaddr_in sin4; struct sockaddr_in6 sin6; struct group *group; struct passwd *passwd; struct pollfd fds[3]; tzset(); pfdev = open("/dev/pf", O_RDWR); if (pfdev == -1) err(1, "open(\"/dev/pf\") failed"); ret = IPPROTO_DIVERT_INIT; setsockopt(fds[1].fd, IPPROTO_IP, IP_DIVERTFL, &ret, sizeof(ret)); setsockopt(fds[2].fd, IPPROTO_IPV6, IP_DIVERTFL, &ret, sizeof(ret)); /* DNS */ if (res_init() == -1) err(1, "res_init"); assert(_res_ext.nsaddr_list[0].ss_family != 0); fds[0].fd = dnssock = socket(_res_ext.nsaddr_list[0].ss_family, SOCK_DGRAM | SOCK_DNS, 0); if (fds[0].fd == -1) err(1, "socket"); if (connect(fds[0].fd, (struct sockaddr *)&_res_ext.nsaddr_list[0], _res_ext.nsaddr_list[0].ss_len) != 0) err(1, "connect"); /* IPv4 divert */ memset(&sin4, 0, sizeof(sin4)); sin4.sin_family = AF_INET; sin4.sin_port = htons(DIVERT_PORT); sin4.sin_addr.s_addr = INADDR_ANY; fds[1].fd = socket(AF_INET, SOCK_RAW, IPPROTO_DIVERT); if (fds[1].fd == -1) err(1, "socket"); if (bind(fds[1].fd, (struct sockaddr *) &sin4, sizeof(sin4)) != 0) err(1, "bind"); /* IPv6 divert */ memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(DIVERT_PORT); sin6.sin6_addr = in6addr_any; fds[2].fd = socket(AF_INET6, SOCK_RAW, IPPROTO_DIVERT); if (fds[2].fd == -1) err(1, "socket"); if (bind(fds[2].fd, (struct sockaddr *) &sin6, sizeof(sin6)) != 0) err(1, "bind"); group = getgrnam("_spamd"); if (group == NULL) err(1, "getgrnam"); endgrent(); passwd = getpwnam("_spamd"); if (passwd == NULL) err(1, "getpwnam"); if (chroot("/var/empty") != 0) err(1, "chroot"); if (setgroups(0, NULL) != 0) err(1, "setgroups"); if (setgid(group->gr_gid) != 0) err(1, "setgid"); if (setuid(passwd->pw_uid) != 0) err(1, "setuid"); fds[0].events = POLLIN; fds[1].events = POLLIN; fds[2].events = POLLIN; #if 0 states[0].af = AF_INET; clock_gettime(CLOCK_MONOTONIC, &states[0].timeout); states[0].timeout.tv_sec++; states[0].addr.in4.s_addr = inet_addr("217.72.192.73"); fds[0].events |= POLLOUT; #endif while (1) { char src[48], dst[48]; struct timespec timestamp; #if DEBUG for (i=0; i < 3; i++) fprintf(stderr, "%d: fd:%d events:%hd revents:%hd\n", i, fds[i].fd, fds[i].events, fds[i].revents); fprintf(stderr, "Polling"); #endif ret = -1; for (i=0; i < NSTATES; i++) if (states[i].af != 0 && (ret == -1 || timespeccmp(&states[i].timeout, &states[ret].timeout, <))) ret = i; if (ret == -1) ret = ppoll(fds, 3, NULL, NULL); else { if (clock_gettime(CLOCK_MONOTONIC, ×tamp) == -1) err(1, "clock_gettime"); timespecsub(&states[ret].timeout, ×tamp, ×tamp); if (timestamp.tv_sec < 0) timestamp.tv_sec = timestamp.tv_nsec = 0; ret = ppoll(fds, 3, ×tamp, NULL); } if (ret == -1) err(1, "poll"); if (clock_gettime(CLOCK_MONOTONIC, ×tamp) == -1) err(1, "clock_gettime"); #if DEBUG for(i=0; i < 3; i++) fprintf(stderr, "%d: fd:%d events:%hd revents:%hd\n", i, fds[i].fd, fds[i].events, fds[i].revents); #endif /* first check for DNS replies and timeouts to free up states. */ if (fds[0].revents & POLLIN) process_response(); /* timeouts */ for (i=0; i < NSTATES; i++) { if (states[i].af != 0 && timespeccmp(&states[i].timeout, ×tamp, <)) enlist(&states[i], 0); } /* send DNS queries ? */ if (fds[0].revents & POLLOUT) { fds[0].events &= ~POLLOUT; for (i=0; i < NSTATES; i++) { if (states[i].af == 0) continue; if (states[i].dnskey == 0) { arc4random_buf(&states[i].dnskey, sizeof(states[i].dnskey)); for (int j = 0; j < sizeof(whitelists) / sizeof(whitelists[0]); j++) { send_query(&states[i], whitelists[j]); } } if (states[i].dnskey == 0) { fds[0].events |= POLLOUT; break; } } } /* Then accept next smtp connects */ if (fds[1].revents & POLLIN) { /* IPv4 */; char packet[IP_MAXPACKET]; const struct ip * const ip = (struct ip *) packet; ret = recv(fds[1].fd, packet, sizeof(packet), MSG_DONTWAIT); if (ret == -1) err(1, "recv"); if (ret < sizeof(struct ip)) { warnx("packet is too short"); continue; } if (inet_ntop(AF_INET, &ip->ip_src, src, sizeof(src)) == NULL) (void)strlcpy(src, "?", sizeof(src)); if (inet_ntop(AF_INET, &ip->ip_dst, dst, sizeof(dst)) == NULL) (void)strlcpy(dst, "?", sizeof(dst)); t = time(NULL); fprintf(stderr, "%.19s: %s -> %s\n", ctime(&t), src, dst); ret = -1; for (i=0; i < NSTATES; i++) { if (states[i].addr.in4.s_addr == ip->ip_src.s_addr) { ret = -2; break; } if (states[i].af == 0) ret = i; } if (ret == -1) warnx("State table full"); else if (ret == -2) warnx("Already seen"); else { struct timespec timeout = { 0, 900000000 }; /* 0,9 s */ states[ret].af = AF_INET; states[ret].addr.in4 = ip->ip_src; timespecadd(×tamp, &timeout, &states[ret].timeout); /* queue dns */ fds[0].events |= POLLOUT; #if DEBUG fprintf(stderr, "Activated state %d for %s\n", ret, name); #endif } } else if (fds[2].revents & POLLIN) { /* IPv6 */; char packet[IPV6_MAXPACKET]; const struct ip6_hdr * const ip6 = (struct ip6_hdr *) packet; ret = recv(fds[2].fd, packet, sizeof(packet), MSG_DONTWAIT); if (ret == -1) err(1, "recv"); if (ret < sizeof(struct ip6_hdr)) { warnx("packet is too short"); continue; } if (inet_ntop(AF_INET6, &ip6->ip6_src, src, sizeof(src)) == NULL) (void)strlcpy(src, "?", sizeof(src)); if (inet_ntop(AF_INET6, &ip6->ip6_dst, dst, sizeof(dst)) == NULL) (void)strlcpy(dst, "?", sizeof(dst)); t = time(NULL); fprintf(stderr, "%.19s: %s -> %s\n", ctime(&t), src, dst); ret = -1; for (i=0; i < NSTATES; i++) { if (!memcmp(&states[i].addr.in6, &ip6->ip6_src, sizeof(ip6->ip6_src))) { ret = -2; break; } if (states[i].af == 0) ret = i; } if (ret == -1) warnx("State table full"); else if (ret == -2) warnx("Already seen"); else { states[ret].af = AF_INET6; states[ret].addr.in6 = ip6->ip6_src; states[ret].timeout = timestamp; states[ret].timeout.tv_sec++; /* 1s timeout */ /* queue dns */ fds[0].events |= POLLOUT; #if DEBUG fprintf(stderr, "Activated state %d for %s\n", ret, name); #endif } } } } void send_query(struct state *state, const char *question) { int ret; uint8_t msg[512]; uint8_t *p = msg + sizeof(struct dns_header); struct dns_header *head = (struct dns_header *)msg; struct dns_record *record; char name[HOST_NAME_MAX]; memset(msg, 0, sizeof(msg)); head->id = htons(state->dnskey); head->flags = htons(RD); /* In practise only one question is supported by nameservers. */ head->qdcount = htons(1); ret = snprintf(name, sizeof(name), "%hhu.%hhu.%hhu.%hhu.%s", state->addr.octets[3], state->addr.octets[2], state->addr.octets[1], state->addr.octets[0], question); if (ret >= sizeof(name)) errx(1, "truncated domain name"); ret = dn_comp(name, p, sizeof(msg) - (p-msg), NULL, NULL); if (ret == -1) errx(1, "dn_comp"); p += ret; record = (struct dns_record *)p; p += 4; /* no ttl or length in the question section */ if (p - msg > sizeof(msg)) errx(1, "buffer too small"); record->type = htons(1); record->class = htons(1); ret = send(dnssock, msg, p - msg, MSG_DONTWAIT); /* TODO: use poll */ if (ret == -1) err(1, "send"); if (ret != p - msg) err(1, "sent short datagram"); } void process_response() { int ret; uint8_t msg[512]; const uint8_t *p; struct dns_header *head = (struct dns_header *)msg; #if DEBUG struct dns_record *record; char name[HOST_NAME_MAX]; #endif memset(msg, 0, sizeof(msg)); ret = recv(dnssock, msg, sizeof(msg), MSG_DONTWAIT); if (ret == -1) err(1, "recv"); if (ret > 1023) warn("Datagram truncated."); msg[sizeof(msg) - 1] = '\0'; #if DEBUG fprintf(stderr, "Received DNS: id %#.4hx flags %#.4hx qdcount%hu ancount%hu nscount%hu arcount%hu\n", ntohs(head->id), ntohs(head->flags), ntohs(head->qdcount), ntohs(head->ancount), ntohs(head->nscount), ntohs(head->arcount)); #endif if ((ntohs(head->flags) & (QR|RCODE_MASK)) == QR) { /* lookup successful */ for (int i=0; i <= NSTATES; i++) if (states[i].dnskey == ntohs(head->id)) { enlist(&states[i], 1); } } else if ((ntohs(head->flags) & RCODE_MASK) >> RCODE_SHIFT == 3) /* fprintf(stderr, "No entry found\n") */; else warnx("DNS response code not understood."); p = msg + sizeof(struct dns_header); #if DEBUG /* Questions */ fprintf(stderr, "QUESTIONS section:\n"); fflush(stderr); for (int i = 0; i < ntohs(head->qdcount); i++) { ret = dn_expand(msg, msg + sizeof(msg), p, name, sizeof(name)); if (ret == -1) errx(1, "dn_expand"); p += ret; record = (struct dns_record *)p; p += 4; /* no ttl or length in the question section */ if (p - msg > sizeof(msg)) { warnx("end of buffer reached."); break; } record->type = htons(1); record->class = htons(1); fprintf(stderr, "QNAME <%s> QTYPE %hu QCLASS %hu\n", name, ntohs(record->type), /* QTYPE */ ntohs(record->class)); /* QCLASS */ } /* Answers */ fprintf(stderr, "ANSWERS section:\n"); fflush(stderr); for (int i = 0; i < ntohs(head->ancount); i++) { ret = dn_expand(msg, msg + sizeof(msg), p, name, sizeof(name)); if (ret == -1) errx(1, "dn_expand"); p += ret; record = (struct dns_record *)p; p += 10; if (p - msg > sizeof(msg)) { warnx("end of buffer reached."); break; } fprintf(stderr, "QNAME <%s> TYPE %hu CLASS %hu TTL %u length %hu data <%s> as IPv4 address: %s\n", name, ntohs(record->type), /* QTYPE */ ntohs(record->class), /* QCLASS */ ntohl(record->ttl), /* TTL */ ntohs(record->length), /* RDATA length */ p, inet_ntoa(*(struct in_addr *)p)); p += ntohs(record->length); } /* Authorities */ fprintf(stderr, "AUTHORITIES section:\n"); fflush(stderr); for (int i = 0; i < ntohs(head->nscount); i++) { ret = dn_expand(msg, msg + sizeof(msg), p, name, sizeof(name)); if (ret == -1) errx(1, "dn_expand"); p += ret; record = (struct dns_record *)p; p += 10; if (p - msg > sizeof(msg)) { warnx("end of buffer reached."); break; } if (ntohs(record->type) == 1 && ntohs(record->class) == 1 && ntohs(record->length) == 4) { if (inet_ntop(AF_INET, p, address, sizeof(address)) == NULL) err(1, "inet_ntop"); } fprintf(stderr, "QNAME <%s> TYPE %hu CLASS %hu TTL %u length %hu data <%s> as IPv4 address: %s\n", name, ntohs(record->type), /* QTYPE */ ntohs(record->class), /* QCLASS */ ntohl(record->ttl), /* TTL */ ntohs(record->length), /* RDATA length */ p, inet_ntoa(*(struct in_addr *)p)); p += ntohs(record->length); } /* Additional */ fprintf(stderr, "ADDITIONAL section:\n"); fflush(stderr); for (int i = 0; i < ntohs(head->arcount); i++) { ret = dn_expand(msg, msg + sizeof(msg), p, name, sizeof(name)); if (ret == -1) errx(1, "dn_expand"); p += ret; record = (struct dns_record *)p; p += 10; if (p - msg > sizeof(msg)) { warnx("end of buffer reached."); break; } if (ntohs(record->type) == 1 && ntohs(record->class) == 1 && ntohs(record->length) == 4) { if (inet_ntop(AF_INET, p, address, sizeof(address)) == NULL) err(1, "inet_ntop"); } fprintf(stderr, "QNAME <%s> TYPE %hu CLASS %hu TTL %u length %hu data <%s> as IPv4 address: %s\n", name, ntohs(record->type), /* QTYPE */ ntohs(record->class), /* QCLASS */ ntohl(record->ttl), /* TTL */ ntohs(record->length), /* RDATA length */ p, inet_ntoa(*(struct in_addr *)p)); p += ntohs(record->length); } #endif } void enlist(struct state *state, int white) { #if 0 int ret; pid_t pid; #endif time_t t; struct pfioc_table pfioc; struct pfr_addr pfaddr; char address[48]; /* add to spamd-white/grey table */ bzero(&pfioc, sizeof(pfioc)); bzero(&pfaddr, sizeof(pfaddr)); strlcpy(pfioc.pfrio_table.pfrt_name, white ? "dnswl-white" : "dnswl-grey", sizeof(pfioc.pfrio_table.pfrt_name)); pfioc.pfrio_buffer = &pfaddr; pfioc.pfrio_esize = sizeof(pfaddr); pfioc.pfrio_size = 1; pfaddr.pfra_af = state->af; switch (state->af) { case AF_INET: pfaddr.pfra_ip4addr = state->addr.in4; pfaddr.pfra_net = 32; break; case AF_INET6: pfaddr.pfra_ip6addr = state->addr.in6; pfaddr.pfra_net = 128; break; default: errx(1, "unknown address family %d", state->af); } if (inet_ntop(pfaddr.pfra_af, &pfaddr.pfra_ip4addr, address, sizeof(address)) == NULL) err(1, "inet_ntop"); if (ioctl(pfdev, DIOCRADDADDRS, &pfioc) == -1) err(1, "cannot add %s to table %s", address, pfioc.pfrio_table.pfrt_name); else { t = time(NULL); fprintf(stderr, "%.19s: added %d %s to table %s\n", ctime(&t), pfioc.pfrio_nadd, address, pfioc.pfrio_table.pfrt_name); } /* add to spamdb database by running the spamdb command. */ #if 0 if (white) { pid = fork(); if (pid == -1) err(1, "fork"); else if (pid == 0) { execle("/usr/sbin/spamdb", "spamdb", "-a", address, NULL, NULL); err(1, "execle"); } do { pid_t pid2; pid2 = waitpid(pid, &ret, 0); if (pid2 == -1) err(1, "waitpid"); assert(pid2 == pid); } while (! WIFEXITED(ret) ); if (WEXITSTATUS(ret) != 0) warnx("spamdb -a %s failed with %d", address, WEXITSTATUS(ret)); } #endif memset(state, 0, sizeof(*state)); } -- http://gmerlin.de OpenPGP: http://gmerlin.de/christopher.pub 2779 7F73 44FD 0736 B67A C410 69EC 7922 34B4 2566 [demime 1.01d removed an attachment of type application/pgp-signature]