spamd: make blacklists override learned whitelist

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

spamd: make blacklists override learned whitelist

Todd C. Miller
When running spamd in greylisting mode, it is not uncommon for an
IP to get whitelisted that later shows up on a spam blacklist.
However, that blacklist entry never takes effect because the IP is
already whitelisted and thus appears in the spamd-white of table,
bypassing spamd.

This is exacerbated by spamlogd which will keep the whitelist entry
updated as long as the IP keeps connecting, which is something
spammers are good at.

The following diff causes spamd to check the blacklists before
adding a WHITE entry to the spamd-white pf table.  If the IP matches
a blacklist, the WHITE entry will be removed.

It has helped a lot on the mailing list server which often receives
spam from an IP and passes greylisting before the IP ends up on a
blacklist.

This does means that you cannot simply use "spamdb -a" to temporarily
override a blacklist, but the proper way to override a blacklist
is via a :white: entry in spamd.conf.

You'll need to apply my "speed up blacklist lookups" diff before
this one.

 - todd

diff -u spamd.bsearch/grey.c spamd.rmblack/grey.c
--- spamd.bsearch/grey.c Tue Oct 17 05:27:37 2017
+++ spamd.rmblack/grey.c Tue Oct 17 05:39:40 2017
@@ -49,6 +49,7 @@
 extern FILE *grey;
 extern int debug;
 extern int syncsend;
+extern int greyback[2];
 
 /* From netinet/in.h, but only _KERNEL_ gets them. */
 #define satosin(sa) ((struct sockaddr_in *)(sa))
@@ -323,6 +324,7 @@
 addwhiteaddr(char *addr)
 {
  struct addrinfo hints, *res;
+ char ch;
 
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_INET; /*for now*/
@@ -330,28 +332,43 @@
  hints.ai_protocol = IPPROTO_UDP; /*dummy*/
  hints.ai_flags = AI_NUMERICHOST;
 
- if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
- if (whitecount == whitealloc) {
- char **tmp;
+ if (getaddrinfo(addr, NULL, &hints, &res) != 0)
+ return(-1);
 
- tmp = reallocarray(whitelist,
-    whitealloc + 1024, sizeof(char *));
- if (tmp == NULL) {
+ /* Check spamd blacklists in main process. */
+ if (send(greyback[0], res->ai_addr, res->ai_addr->sa_len, 0) == -1) {
+ syslog_r(LOG_ERR, &sdata, "%s: send: %m", __func__);
+ } else {
+ if (recv(greyback[0], &ch, sizeof(ch), 0) == 1) {
+ if (ch == '1') {
+ syslog_r(LOG_DEBUG, &sdata,
+    "%s blacklisted, removing from whitelist",
+    addr);
  freeaddrinfo(res);
  return(-1);
  }
- whitelist = tmp;
- whitealloc += 1024;
  }
- whitelist[whitecount] = strdup(addr);
- if (whitelist[whitecount] == NULL) {
+ }
+
+ if (whitecount == whitealloc) {
+ char **tmp;
+
+ tmp = reallocarray(whitelist,
+    whitealloc + 1024, sizeof(char *));
+ if (tmp == NULL) {
  freeaddrinfo(res);
  return(-1);
  }
- whitecount++;
+ whitelist = tmp;
+ whitealloc += 1024;
+ }
+ whitelist[whitecount] = strdup(addr);
+ if (whitelist[whitecount] == NULL) {
  freeaddrinfo(res);
- } else
  return(-1);
+ }
+ whitecount++;
+ freeaddrinfo(res);
  return(0);
 }
 
diff -u spamd.bsearch/sdl.c spamd.rmblack/sdl.c
--- spamd.bsearch/sdl.c Tue Oct 17 05:28:46 2017
+++ spamd.rmblack/sdl.c Mon Oct 16 17:36:53 2017
@@ -431,6 +431,50 @@
  }
 }
 
+static int
+sdl_check_v4(struct sdlist *sdl, struct in_addr *src)
+{
+ while (sdl->tag != NULL) {
+ if (bsearch(src, sdl->v4.addrs, sdl->v4.naddrs,
+    sizeof(struct sdentry_v4), match_addr_v4) != NULL)
+ return (1);
+ sdl++;
+ }
+ return (0);
+}
+
+static int
+sdl_check_v6(struct sdlist *sdl, struct sdaddr_v6 *src)
+{
+ while (sdl->tag != NULL) {
+ if (bsearch(src, sdl->v6.addrs, sdl->v6.naddrs,
+    sizeof(struct sdentry_v6), match_addr_v6) != NULL)
+ return (1);
+ sdl++;
+ }
+ return (0);
+}
+
+/*
+ * Given an address and address family
+ * returns 1 if address is on a blacklist, else 0.
+ */
+int
+sdl_check(struct sdlist *head, int af, void *src)
+{
+ if (head == NULL)
+ return (0);
+
+ switch (af) {
+ case AF_INET:
+ return (sdl_check_v4(head, src));
+ case AF_INET6:
+ return (sdl_check_v6(head, src));
+ default:
+ return (0);
+ }
+}
+
 static void
 sdl_free(struct sdlist *sdl)
 {
diff -u spamd.bsearch/sdl.h spamd.rmblack/sdl.h
--- spamd.bsearch/sdl.h Tue Oct 17 05:28:10 2017
+++ spamd.rmblack/sdl.h Mon Oct 16 17:36:53 2017
@@ -62,6 +62,7 @@
 
 int sdl_add(char *, char *, char **, u_int, char **, u_int);
 void sdl_del(char *);
+int sdl_check(struct sdlist *, int, void *);
 struct sdlist **sdl_lookup(struct sdlist *, int, void *);
 
 #endif /* _SDL_H_ */
diff -u spamd.bsearch/spamd.c spamd.rmblack/spamd.c
--- spamd.bsearch/spamd.c Tue Oct 17 05:27:53 2017
+++ spamd.rmblack/spamd.c Mon Oct 16 17:36:53 2017
@@ -115,11 +115,13 @@
 int      read_configline(FILE *);
 void spamd_tls_init(void);
 void check_spamd_db(void);
+void blackcheck(int);
 
 char hostname[HOST_NAME_MAX+1];
 struct syslog_data sdata = SYSLOG_DATA_INIT;
 char *nreply = "450";
 char *spamd = "spamd IP-based SPAM blocker";
+int greyback[2];
 int greypipe[2];
 int trappipe[2];
 FILE *grey;
@@ -1226,7 +1228,8 @@
 #define PFD_SYNCFD 2
 #define PFD_CONFFD 3
 #define PFD_TRAPFD 4
-#define PFD_FIRSTCON 5
+#define PFD_GREYBACK 5
+#define PFD_FIRSTCON 6
 
 int
 main(int argc, char *argv[])
@@ -1480,6 +1483,10 @@
  maxblack = 0;
 
  /* open pipe to talk to greylister */
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, greyback) == -1) {
+ syslog(LOG_ERR, "socketpair (%m)");
+ exit(1);
+ }
  if (pipe(greypipe) == -1) {
  syslog(LOG_ERR, "pipe (%m)");
  exit(1);
@@ -1502,6 +1509,7 @@
  syslog(LOG_ERR, "fdopen (%m)");
  _exit(1);
  }
+ close(greyback[0]);
  close(greypipe[0]);
  trapfd = trappipe[0];
  trapcfg = fdopen(trappipe[0], "r");
@@ -1528,6 +1536,7 @@
  goto jail;
  }
  /* parent - run greylister */
+ close(greyback[1]);
  grey = fdopen(greypipe[0], "r");
  if (grey == NULL) {
  syslog(LOG_ERR, "fdopen (%m)");
@@ -1574,6 +1583,13 @@
  pfd[PFD_SYNCFD].fd = -1;
  pfd[PFD_SYNCFD].events = 0;
  }
+ if (greylist) {
+ pfd[PFD_GREYBACK].fd = greyback[1];
+ pfd[PFD_GREYBACK].events = POLLIN;
+ } else {
+ pfd[PFD_GREYBACK].fd = -1;
+ pfd[PFD_GREYBACK].events = 0;
+ }
 
  /* events and pfd entries for con[] are filled in below. */
  pfd[PFD_SMTPLISTEN].fd = smtplisten;
@@ -1736,6 +1752,44 @@
  read_configline(trapcfg);
  if (pfd[PFD_SYNCFD].revents & (POLLIN|POLLHUP))
  sync_recv();
+ if (pfd[PFD_GREYBACK].revents & (POLLIN|POLLHUP))
+ blackcheck(greyback[1]);
  }
  exit(1);
+}
+
+void
+blackcheck(int fd)
+{
+ struct sockaddr_storage ss;
+ ssize_t nread;
+ void *ia;
+ char ch;
+
+ /* Read sockaddr from greylister and look it up in the blacklists. */
+ nread = recv(fd, &ss, sizeof(ss), 0);
+ if (nread == -1) {
+ syslog(LOG_ERR, "%s: recv: %m", __func__);
+ return;
+ }
+ if (nread != sizeof(struct sockaddr_in) &&
+    nread != sizeof(struct sockaddr_in6)) {
+ syslog(LOG_ERR, "%s: invalid size %zd", __func__, nread);
+ return;
+ }
+ if (ss.ss_family == AF_INET) {
+ ia = &((struct sockaddr_in *)&ss)->sin_addr;
+ } else if (ss.ss_family == AF_INET6) {
+ ia = &((struct sockaddr_in6 *)&ss)->sin6_addr;
+ } else {
+ syslog(LOG_ERR, "%s: bad family %d", __func__, ss.ss_family);
+ return;
+ }
+ ch = sdl_check(blacklists, ss.ss_family, ia) ? '1' : '0';
+
+ /* Send '1' for match or '0' for no match. */
+ if (send(fd, &ch, sizeof(ch), 0) == -1) {
+ syslog(LOG_ERR, "%s: send: %m", __func__);
+ return;
+ }
 }