summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarius Halden <marius.h@lden.org>2016-01-11 01:37:26 +0100
committerMarius Halden <marius.h@lden.org>2016-01-11 01:37:26 +0100
commitafc2c4818834efb1fd45c267515ef26e674f63f8 (patch)
treeef1f8d103a0bd852f16057c2f6d7e76bb2a626fd
downloadspamd-patches-afc2c4818834efb1fd45c267515ef26e674f63f8.tar.gz
spamd-patches-afc2c4818834efb1fd45c267515ef26e674f63f8.tar.bz2
spamd-patches-afc2c4818834efb1fd45c267515ef26e674f63f8.tar.xz
initial
-rw-r--r--data_structures27
-rw-r--r--spamd-setup.patch940
-rw-r--r--spamd-setup.patch_1939
-rw-r--r--spamd.patch938
-rw-r--r--spamlogd.patch78
5 files changed, 2922 insertions, 0 deletions
diff --git a/data_structures b/data_structures
new file mode 100644
index 0000000..3a1c570
--- /dev/null
+++ b/data_structures
@@ -0,0 +1,27 @@
+
+struct ipv6 {
+ union {
+ struct in6_addr addr;
+ u_int32_t addr32[4];
+ } _ipv6;
+#define addr32 _ipv6.addr32
+};
+
+struct cidr {
+ sa_family_t ai_family;
+ union {
+ u_int32_t addr4;
+ struct ipv6 addr6;
+ };
+ u_int8_t bits;
+};
+
+struct bl {
+ sa_family_t ai_family;
+ union {
+ u_int32_t addr4;
+ struct ipv6 addr6;
+ };
+ int8_t b;
+ int8_t w;
+};
diff --git a/spamd-setup.patch b/spamd-setup.patch
new file mode 100644
index 0000000..403fc19
--- /dev/null
+++ b/spamd-setup.patch
@@ -0,0 +1,940 @@
+? spamd-setup
+Index: spamd-setup.c
+===================================================================
+RCS file: /cvs/src/libexec/spamd-setup/spamd-setup.c,v
+retrieving revision 1.47
+diff -u -p -u -r1.47 spamd-setup.c
+--- spamd-setup.c 12 Dec 2015 20:09:28 -0000 1.47
++++ spamd-setup.c 10 Jan 2016 22:08:43 -0000
+@@ -30,6 +30,7 @@
+ #include <errno.h>
+ #include <fcntl.h>
+ #include <stdio.h>
++#include <stdint.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <unistd.h>
+@@ -42,13 +43,29 @@
+ #define PATH_SPAMD_CONF "/etc/mail/spamd.conf"
+ #define SPAMD_ARG_MAX 256 /* max # of args to an exec */
+
++struct ipv6 {
++ union {
++ u_int8_t addr8[16];
++ u_int16_t addr16[8];
++ u_int32_t addr32[4];
++ };
++};
++
+ struct cidr {
+- u_int32_t addr;
++ //sa_family_t ai_family;
++ union {
++ u_int32_t addr4;
++ struct ipv6 addr6;
++ };
+ u_int8_t bits;
+ };
+
+ struct bl {
+- u_int32_t addr;
++ //sa_family_t ai_family;
++ union {
++ u_int32_t addr4;
++ struct ipv6 addr6;
++ };
+ int8_t b;
+ int8_t w;
+ };
+@@ -58,6 +75,8 @@ struct blacklist {
+ char *message;
+ struct bl *bl;
+ size_t blc, bls;
++ struct bl *bl6;
++ size_t blc6, bls6;
+ u_int8_t black;
+ };
+
+@@ -66,19 +85,30 @@ u_int8_t maxblock(u_int32_t, u_int8_t);
+ u_int8_t maxdiff(u_int32_t, u_int32_t);
+ struct cidr *range2cidrlist(struct cidr *, u_int *, u_int *, u_int32_t,
+ u_int32_t);
++struct cidr *range2cidrlist6(struct cidr *, u_int *, u_int *, struct ipv6 *,
++ struct ipv6 *);
+ void cidr2range(struct cidr, u_int32_t *, u_int32_t *);
++void cidr2range6(struct cidr, struct ipv6 *, struct ipv6 *);
+ char *atop(u_int32_t);
+-int parse_netblock(char *, struct bl *, struct bl *, int);
++int parse_netblock(char *, struct bl *, struct bl *, struct bl*,
++ struct bl *, int);
+ int open_child(char *, char **);
+ int fileget(char *);
+ int open_file(char *, char *);
+ char *fix_quoted_colons(char *);
+ void do_message(FILE *, char *);
+-struct bl *add_blacklist(struct bl *, size_t *, size_t *, gzFile, int);
++int add_blacklist(struct bl **, size_t *, size_t *, struct bl **,
++ size_t *, size_t *, gzFile, int);
++int cmp_ipv6(const struct ipv6 *, const struct ipv6 *);
+ int cmpbl(const void *, const void *);
+-struct cidr *collapse_blacklist(struct bl *, size_t, u_int *);
+-int configure_spamd(u_short, char *, char *, struct cidr *, u_int);
+-int configure_pf(struct cidr *);
++int cmpbl6(const void *, const void *);
++int collapse_blacklist(struct bl *, size_t, struct bl *, size_t,
++ struct cidr **, u_int *, struct cidr **, u_int *);
++struct cidr *collapse_blacklist4(struct bl *, size_t, u_int *);
++struct cidr *collapse_blacklist6(struct bl *, size_t, u_int *);
++int configure_spamd(u_short, char *, char *, struct cidr *, u_int,
++ struct cidr *, u_int);
++int configure_pf(struct cidr *, u_int, struct cidr *, u_int);
+ int getlist(char **, char *, struct blacklist *, struct blacklist *);
+ __dead void usage(void);
+
+@@ -89,6 +119,77 @@ int greyonly = 1;
+ extern char *__progname;
+
+ #define MAXIMUM(a,b) (((a)>(b))?(a):(b))
++#define MINIMUM(a,b) (((a)<(b))?(a):(b))
++
++void
++ipv6_add(struct ipv6 *addr, u_int8_t num)
++{
++ int i;
++ u_int8_t carry;
++ u_int32_t tmp;
++
++ for (i = 3; i >= 0; i--) {
++ if (num == 0)
++ break;
++
++ tmp = ntohl(addr->addr32[i]);
++ if (num > (UINT32_MAX - tmp))
++ carry = 1;
++ else
++ carry = 0;
++
++ addr->addr32[i] = htonl(tmp + num);
++ num = carry;
++ }
++}
++
++void
++ipv6_add2(struct ipv6 *addr, struct ipv6 *src)
++{
++ int i, j;
++ u_int8_t carry;
++ u_int32_t num, tmp;
++
++ for (i = 3; i >= 0; i--) {
++ num = ntohl(src->addr32[i]);
++
++ for (j = i; j >= 0; j--) {
++ if (num == 0)
++ break;
++
++ tmp = ntohl(addr->addr32[j]);
++ if (num > (UINT32_MAX - tmp))
++ carry = 1;
++ else
++ carry = 0;
++
++ addr->addr32[j] = htonl(tmp + num);
++ num = carry;
++ }
++ }
++}
++
++void
++ipv6_sub(struct ipv6 *addr, u_int8_t num)
++{
++ int i;
++ u_int8_t carry;
++ u_int32_t tmp;
++
++ for (i = 3; i >= 0; i--) {
++ if (num == 0)
++ break;
++
++ tmp = ntohl(addr->addr32[i]);
++ if (num > tmp)
++ carry = 1;
++ else
++ carry = 0;
++
++ addr->addr32[i] = htonl(tmp - num);
++ num = carry;
++ }
++}
+
+ u_int32_t
+ imask(u_int8_t b)
+@@ -98,6 +199,24 @@ imask(u_int8_t b)
+ return (0xffffffffU << (32 - b));
+ }
+
++void
++imask6(struct ipv6 *m, u_int8_t b)
++{
++ int i;
++ u_int8_t tmp;
++ memset(m, 0, sizeof(struct ipv6));
++
++ for (i = 0; i < 4; i++) {
++ if (b == 0)
++ break;
++
++ tmp = MINIMUM(b, 32);
++ b -= tmp;
++
++ m->addr32[i] = htonl(0xffffffffU << (32 - tmp));
++ }
++}
++
+ u_int8_t
+ maxblock(u_int32_t addr, u_int8_t bits)
+ {
+@@ -114,6 +233,26 @@ maxblock(u_int32_t addr, u_int8_t bits)
+ }
+
+ u_int8_t
++maxblock6(struct ipv6 *addr, u_int8_t bits)
++{
++ struct ipv6 m;
++ int i;
++
++ while (bits > 0) {
++ imask6(&m, bits - 1);
++
++ for (i = 0; i < 4; i++) {
++ if ((addr->addr32[i] & m.addr32[i]) != addr->addr32[i]) {
++ return bits;
++ }
++ }
++
++ bits--;
++ }
++ return (bits);
++}
++
++u_int8_t
+ maxdiff(u_int32_t a, u_int32_t b)
+ {
+ u_int8_t bits = 0;
+@@ -130,6 +269,28 @@ maxdiff(u_int32_t a, u_int32_t b)
+ return (bits);
+ }
+
++u_int8_t
++maxdiff6(struct ipv6 *a, struct ipv6 *b)
++{
++ u_int8_t bits = 0;
++ struct ipv6 m, tmp;
++ int i;
++
++ memcpy(&tmp, b, sizeof(tmp));
++
++ ipv6_add(&tmp, 1);
++ while (bits < 128) {
++ imask6(&m, bits);
++
++ for (i = 0; i < 4; i++) {
++ if ((a->addr32[i] & m.addr32[i]) != (tmp.addr32[i] & m.addr32[i]))
++ return (bits);
++ }
++ bits++;
++ }
++ return (bits);
++}
++
+ struct cidr *
+ range2cidrlist(struct cidr *list, u_int *cli, u_int *cls, u_int32_t start,
+ u_int32_t end)
+@@ -150,7 +311,7 @@ range2cidrlist(struct cidr *list, u_int
+ list = tmp;
+ *cls += 32;
+ }
+- list[*cli].addr = start;
++ list[*cli].addr4 = start;
+ list[*cli].bits = maxsize;
+ (*cli)++;
+ start = start + (1 << (32 - maxsize));
+@@ -158,11 +319,81 @@ range2cidrlist(struct cidr *list, u_int
+ return (list);
+ }
+
++struct cidr *
++range2cidrlist6(struct cidr *list, u_int *cli, u_int *cls, struct ipv6 *start,
++ struct ipv6 *end)
++{
++ u_int8_t maxsize, diff, btmp;
++ struct cidr *tmp;
++ struct ipv6 mask;
++ int i;
++
++ while (cmp_ipv6(end, start) >= 0) {
++ maxsize = maxblock6(start, 128);
++ diff = maxdiff6(start, end);
++
++ maxsize = MAXIMUM(maxsize, diff);
++ if (*cls <= *cli + 1) {
++ tmp = reallocarray(list, *cls + 32,
++ sizeof(struct cidr));
++ if (tmp == NULL)
++ err(1, NULL);
++ list = tmp;
++ *cls += 32;
++ }
++ memcpy(&list[*cli].addr6, start,
++ sizeof(list[*cli].addr6));
++ list[*cli].bits = maxsize;
++ (*cli)++;
++
++ maxsize = 128 - maxsize;
++
++ memset(&mask, 0, sizeof(mask));
++ for (i = 15; i >= 0; i--) {
++ btmp = MINIMUM(maxsize, 8);
++ maxsize -= btmp;
++
++ mask.addr8[i] = ((1 << btmp) - 1);
++
++ if (maxsize == 0)
++ break;
++ }
++
++ ipv6_add(&mask, 1);
++ ipv6_add2(start, &mask);
++ }
++
++ return (list);
++}
++
+ void
+ cidr2range(struct cidr cidr, u_int32_t *start, u_int32_t *end)
+ {
+- *start = cidr.addr;
+- *end = cidr.addr + (1 << (32 - cidr.bits)) - 1;
++ *start = cidr.addr4;
++ *end = cidr.addr4 + (1 << (32 - cidr.bits)) - 1;
++}
++
++void
++cidr2range6(struct cidr cidr, struct ipv6 *start, struct ipv6 *end)
++{
++ int i;
++ u_int8_t tmp, bits = 128 - cidr.bits;
++ struct ipv6 mask;
++ memset(&mask, 0, sizeof(mask));
++
++ memcpy(start, &cidr.addr6, sizeof(cidr.addr6));
++ memcpy(end, &cidr.addr6, sizeof(cidr.addr6));
++
++ for (i = 15; i >= 0; i--) {
++ if (bits == 0)
++ break;
++
++ tmp = MINIMUM(bits, 8);
++ bits -= tmp;
++ mask.addr8[i] = ((1 << tmp) - 1);
++ }
++
++ ipv6_add2(end, &mask);
+ }
+
+ char *
+@@ -176,11 +407,13 @@ atop(u_int32_t addr)
+ }
+
+ int
+-parse_netblock(char *buf, struct bl *start, struct bl *end, int white)
++parse_netblock(char *buf, struct bl *start, struct bl *end, struct bl *start6,
++ struct bl *end6, int white)
+ {
+- char astring[16], astring2[16];
++ char astring[40], astring2[40];
+ unsigned maskbits;
+ struct cidr c;
++ int ret = 0;
+
+ /* skip leading spaces */
+ while (*buf == ' ')
+@@ -189,56 +422,130 @@ parse_netblock(char *buf, struct bl *sta
+ if (*buf == '#')
+ return (0);
+ /* otherwise, look for a netblock of some sort */
+- if (sscanf(buf, "%15[^/]/%u", astring, &maskbits) == 2) {
+- /* looks like a cidr */
+- memset(&c.addr, 0, sizeof(c.addr));
+- if (inet_net_pton(AF_INET, astring, &c.addr, sizeof(c.addr))
+- == -1)
++ if (sscanf(buf, "%15[0123456789.]/%u", astring, &maskbits) == 2) {
++ /* looks like IPv4 cidr */
++ memset(&c.addr4, 0, sizeof(c.addr4));
++ if (inet_net_pton(AF_INET, astring, &c.addr4,
++ sizeof(c.addr4)) == -1)
+ return (0);
+- c.addr = ntohl(c.addr);
++ //c.ai_family = AF_INET;
++ c.addr4 = ntohl(c.addr4);
+ if (maskbits > 32)
+ return (0);
+ c.bits = maskbits;
+- cidr2range(c, &start->addr, &end->addr);
+- end->addr += 1;
++ cidr2range(c, &start->addr4, &end->addr4);
++ //start->ai_family = end->ai_family = AF_INET;
++ end->addr4 += 1;
++
++ ret = AF_INET;
++ } else if (sscanf(buf, "%39[0123456789ABCDEFabcdef:]/%u",
++ astring, &maskbits) == 2) {
++ /* looks like IPv6 cidr */
++ memset(&c.addr6, 0, sizeof(c.addr6));
++ if (inet_net_pton(AF_INET6, astring, &c.addr6,
++ sizeof(c.addr6)) == -1)
++ return (0);
++ //c.ai_family = AF_INET6;
++ if (maskbits > 128)
++ return (0);
++ c.bits = maskbits;
++ cidr2range6(c, &start6->addr6, &end6->addr6);
++ //start6->ai_family = end6->ai_family = AF_INET6;
++ ipv6_add(&end6->addr6, 1);
++
++ ret = AF_INET6;
+ } else if (sscanf(buf, "%15[0123456789.]%*[ -]%15[0123456789.]",
+ astring, astring2) == 2) {
+- /* looks like start - end */
+- memset(&start->addr, 0, sizeof(start->addr));
+- memset(&end->addr, 0, sizeof(end->addr));
+- if (inet_net_pton(AF_INET, astring, &start->addr,
+- sizeof(start->addr)) == -1)
+- return (0);
+- start->addr = ntohl(start->addr);
+- if (inet_net_pton(AF_INET, astring2, &end->addr,
+- sizeof(end->addr)) == -1)
++ /* looks like IPv4 start - end */
++ memset(&start->addr4, 0, sizeof(start->addr4));
++ memset(&end->addr4, 0, sizeof(end->addr4));
++ if (inet_net_pton(AF_INET, astring, &start->addr4,
++ sizeof(start->addr4)) == -1)
+ return (0);
+- end->addr = ntohl(end->addr) + 1;
++ //start->ai_family = AF_INET;
++ start->addr4 = ntohl(start->addr4);
++ if (inet_net_pton(AF_INET, astring2, &end->addr4,
++ sizeof(end->addr4)) == -1)
++ return (0);
++ //end->ai_family = AF_INET;
++ end->addr4 = ntohl(end->addr4) + 1;
+ if (start > end)
+ return (0);
++
++ ret = AF_INET;
++ } else if (sscanf(buf, "%39[0123456789ABCDEFabcdef:]%*[ -]"
++ "%39[0123456789ABCDEFabcdef:]", astring, astring2) == 2) {
++ /* loks like IPv6 start - end */
++ memset(&start6->addr6, 0, sizeof(start6->addr6));
++ memset(&end6->addr6, 0, sizeof(end6->addr6));
++ if (inet_net_pton(AF_INET6, astring, &start6->addr6,
++ sizeof(start6->addr6)) == -1)
++ return (0);
++ //start6->ai_family = AF_INET6;
++ if (inet_net_pton(AF_INET6, astring2, &end6->addr6,
++ sizeof(end6->addr6)) == -1)
++ return (0);
++ //end6->ai_family = AF_INET6;
++ ipv6_add(&end6->addr6, 1);
++ if (start6 > end6)
++ return (0);
++
++ ret = AF_INET6;
+ } else if (sscanf(buf, "%15[0123456789.]", astring) == 1) {
+- /* just a single address */
+- memset(&start->addr, 0, sizeof(start->addr));
+- if (inet_net_pton(AF_INET, astring, &start->addr,
+- sizeof(start->addr)) == -1)
++ /* just a single IPv4 address */
++ memset(&start->addr4, 0, sizeof(start->addr4));
++ if (inet_net_pton(AF_INET, astring, &start->addr4,
++ sizeof(start->addr4)) == -1)
+ return (0);
+- start->addr = ntohl(start->addr);
+- end->addr = start->addr + 1;
++ //start->ai_family = AF_INET;
++ //end->ai_family = AF_INET;
++ start->addr4 = ntohl(start->addr4);
++ end->addr4 = start->addr4 + 1;
++
++ ret = AF_INET;
++ } else if (sscanf(buf, "%39[0123456789ABCDEFabcdef:]", astring) == 1) {
++ /* just a single IPv6 address */
++ memset(&start6->addr6, 0, sizeof(start6->addr6));
++ if (inet_net_pton(AF_INET6, astring, &start6->addr6,
++ sizeof(start6->addr6)) == -1)
++ return (0);
++ //start6->ai_family = AF_INET6;
++ //end6->ai_family = AF_INET6;
++ memcpy(&end6->addr6, &start6->addr6,
++ sizeof(start6->addr6));
++
++ ipv6_add(&end6->addr6, 1);
++
++ ret = AF_INET6;
+ } else
+ return (0);
+
+ if (white) {
+- start->b = 0;
+- start->w = 1;
+- end->b = 0;
+- end->w = -1;
++ if (ret == AF_INET) {
++ start->b = 0;
++ start->w = 1;
++ end->b = 0;
++ end->w = -1;
++ } else {
++ start6->b = 0;
++ start6->w = 1;
++ end->b = 0;
++ end->w = -1;
++ }
+ } else {
+- start->b = 1;
+- start->w = 0;
+- end->b = -1;
+- end->w = 0;
++ if (ret == AF_INET) {
++ start->b = 1;
++ start->w = 0;
++ end->b = -1;
++ end->w = 0;
++ } else {
++ start6->b = 1;
++ start6->w = 0;
++ end6->b = -1;
++ end6->w = 0;
++ }
+ }
+- return (1);
++ return (ret);
+ }
+
+ int
+@@ -445,8 +752,9 @@ do_message(FILE *sdc, char *msg)
+ }
+
+ /* retrieve a list from fd. add to blacklist bl */
+-struct bl *
+-add_blacklist(struct bl *bl, size_t *blc, size_t *bls, gzFile gzf, int white)
++int
++add_blacklist(struct bl **bl, size_t *blc, size_t *bls, struct bl **bl6,
++ size_t *blc6, size_t *bls6, gzFile gzf, int white)
+ {
+ int i, n, start, bu = 0, bs = 0, serrno = 0;
+ char *buf = NULL, *tmp;
+@@ -481,30 +789,55 @@ add_blacklist(struct bl *bl, size_t *blc
+ /* we assume that there is an IP for every 14 bytes */
+ if (*blc + bu / 7 >= *bls) {
+ *bls += bu / 7;
+- blt = reallocarray(bl, *bls, sizeof(struct bl));
++ blt = reallocarray(*bl, *bls, sizeof(struct bl));
+ if (blt == NULL) {
+ *bls -= bu / 7;
+ serrno = errno;
+ goto bldone;
+ }
+- bl = blt;
++ *bl = blt;
++ }
++ if (*blc6 + bu / 7 >= *bls6) {
++ *bls6 += bu / 7;
++ blt = reallocarray(*bl6, *bls6, sizeof(struct bl));
++ if (blt == NULL) {
++ *bls6 -= bu / 7;
++ serrno = errno;
++ goto bldone;
++ }
++ *bl6 = blt;
+ }
+ for (i = 0; i <= bu; i++) {
+ if (*blc + 1 >= *bls) {
+ *bls += 1024;
+- blt = reallocarray(bl, *bls, sizeof(struct bl));
++ blt = reallocarray(*bl, *bls, sizeof(struct bl));
+ if (blt == NULL) {
+ *bls -= 1024;
+ serrno = errno;
+ goto bldone;
+ }
+- bl = blt;
++ *bl = blt;
++ }
++ if (*blc6 + 1 >= *bls6) {
++ *bls6 += 1024;
++ blt = reallocarray(*bl6, *bls6, sizeof(struct bl));
++ if (blt == NULL) {
++ *bls6 -= 1024;
++ serrno = errno;
++ goto bldone;
++ }
++ *bl6 = blt;
+ }
+ if (i == bu || buf[i] == '\n') {
+ buf[i] = '\0';
+- if (parse_netblock(buf + start,
+- bl + *blc, bl + *blc + 1, white))
++ n = parse_netblock(buf + start,
++ *bl + *blc, *bl + *blc + 1,
++ *bl6 + *blc6, *bl6 + *blc6 + 1, white);
++
++ if (n == AF_INET)
+ *blc += 2;
++ else if (n == AF_INET6)
++ *blc6 += 2;
+ start = i + 1;
+ }
+ }
+@@ -512,29 +845,83 @@ add_blacklist(struct bl *bl, size_t *blc
+ errno = EIO;
+ bldone:
+ free(buf);
+- if (serrno)
++ if (serrno) {
+ errno = serrno;
+- return (bl);
++ return (0);
++ }
++ return (1);;
++}
++
++int
++cmp_ipv6(const struct ipv6 *a, const struct ipv6 *b)
++{
++ int i;
++ u_int32_t atmp, btmp;
++
++ for (i = 0; i < 4; i++) {
++ atmp = ntohl(a->addr32[i]);
++ btmp = ntohl(b->addr32[i]);
++
++ if (atmp > btmp)
++ return (1);
++ if (atmp < btmp)
++ return (-1);
++ }
++ return (0);
+ }
+
+ int
+ cmpbl(const void *a, const void *b)
+ {
+- if (((struct bl *)a)->addr > ((struct bl *) b)->addr)
++ if (((struct bl *)a)->addr4 > ((struct bl *) b)->addr4)
+ return (1);
+- if (((struct bl *)a)->addr < ((struct bl *) b)->addr)
++ if (((struct bl *)a)->addr4 < ((struct bl *) b)->addr4)
+ return (-1);
+ return (0);
+ }
+
++int
++cmpbl6(const void *a, const void *b)
++{
++ struct ipv6 *addr1, *addr2;
++
++ addr1 = &((struct bl *)a)->addr6;
++ addr2 = &((struct bl *)b)->addr6;
++
++ return cmp_ipv6(addr1, addr2);
++}
++
+ /*
+ * collapse_blacklist takes blacklist/whitelist entries sorts, removes
+ * overlaps and whitelist portions, and returns netblocks to blacklist
+ * as lists of nonoverlapping cidr blocks suitable for feeding in
+ * printable form to pfctl or spamd.
+ */
++
++int
++collapse_blacklist(struct bl *bl, size_t blc, struct bl *bl6, size_t blc6,
++ struct cidr **cidr, u_int *clc, struct cidr **cidr6, u_int *clc6)
++{
++ if (blc == 0 && blc6 == 0)
++ return (0);
++
++ if (blc > 0) {
++ *cidr = collapse_blacklist4(bl, blc, clc);
++ if (*cidr == NULL)
++ return (0);
++ }
++
++ if (blc6 > 0) {
++ *cidr6 = collapse_blacklist6(bl6, blc6, clc6);
++ if (*cidr6 == NULL)
++ return (0);
++ }
++
++ return (1);
++}
++
+ struct cidr *
+-collapse_blacklist(struct bl *bl, size_t blc, u_int *clc)
++collapse_blacklist4(struct bl *bl, size_t blc, u_int *clc)
+ {
+ int bs = 0, ws = 0, state=0;
+ u_int cli, cls, i;
+@@ -558,13 +945,13 @@ collapse_blacklist(struct bl *bl, size_t
+ qsort(bl, blc, sizeof(struct bl), cmpbl);
+ for (i = 0; i < blc;) {
+ laststate = state;
+- addr = bl[i].addr;
++ addr = bl[i].addr4;
+
+ do {
+ bs += bl[i].b;
+ ws += bl[i].w;
+ i++;
+- } while (bl[i].addr == addr);
++ } while (bl[i].addr4 == addr);
+ if (state == 1 && bs == 0)
+ state = 0;
+ else if (state == 0 && bs > 0)
+@@ -581,18 +968,73 @@ collapse_blacklist(struct bl *bl, size_t
+ }
+ laststate = state;
+ }
+- cl[cli].addr = 0;
++ cl[cli].addr4 = 0;
+ *clc = cli;
+ return (cl);
+ }
+
++struct cidr *
++collapse_blacklist6(struct bl *bl6, size_t blc6, u_int *clc6)
++{
++ int bs = 0, ws = 0, state = 0;
++ u_int cli, cls, i;
++ struct cidr *cl;
++ int laststate;
++ struct ipv6 bstart, addr;
++
++ memset(&bstart, 0, sizeof(bstart));
++
++ if (blc6 == 0)
++ return (NULL);
++
++ cli = 0;
++ cls = (blc6 / 2) + (blc6 / 20) + 1;
++ cl = reallocarray(NULL, cls, sizeof(struct cidr));
++ if (cl == NULL)
++ return (NULL);
++ qsort(bl6, blc6, sizeof(struct bl), cmpbl6);
++
++ for (i = 0; i < blc6;) {
++ laststate = state;
++ memcpy(&addr, &bl6[i].addr6, sizeof(addr));
++
++ do {
++ bs += bl6[i].b;
++ ws += bl6[i].w;
++ i++;
++ } while (cmp_ipv6(&bl6[i].addr6, &addr) == 0);
++ if (state == 1 && bs == 0)
++ state = 0;
++ else if (state == 0 && bs > 0)
++ state = 1;
++ if (ws > 0)
++ state = 0;
++ if (laststate == 0 && state == 1) {
++ /* start blacklist */
++ memcpy(&bstart, &addr, sizeof(addr));
++ }
++ if (laststate == 1 && state == 0) {
++ /* end blacklist */
++ ipv6_sub(&addr, 1);
++ cl = range2cidrlist6(cl, &cli, &cls, &bstart, &addr);
++ }
++ laststate = state;
++ }
++
++ memset(&cl[cli].addr6, 0, sizeof(cl[cli].addr6));
++ *clc6 = cli;
++
++ return (cl);
++}
++
+ int
+ configure_spamd(u_short dport, char *name, char *message,
+- struct cidr *blacklists, u_int count)
++ struct cidr *blacklists, u_int count, struct cidr *blacklists6, u_int count6)
+ {
+- int lport = IPPORT_RESERVED - 1, s;
++ int lport = IPPORT_RESERVED - 1, s, i;
+ struct sockaddr_in sin;
+ FILE* sdc;
++ char buf[INET6_ADDRSTRLEN];
+
+ s = rresvport(&lport);
+ if (s == -1)
+@@ -612,10 +1054,14 @@ configure_spamd(u_short dport, char *nam
+ fputs(name, sdc);
+ do_message(sdc, message);
+ fprintf(sdc, ";inet;%u", count);
+- while (blacklists->addr != 0) {
+- fprintf(sdc, ";%s/%u", atop(blacklists->addr),
+- blacklists->bits);
+- blacklists++;
++ for (i = 0; i < count; i++) {
++ fprintf(sdc, ";%s/%u", atop(blacklists[i].addr4),
++ blacklists[i].bits);
++ }
++ fprintf(sdc, ";inet6;%u", count6);
++ for (i = 0; i < count6; i++) {
++ inet_ntop(AF_INET6, &blacklists6[i].addr6, buf, sizeof(buf));
++ fprintf(sdc, ";%s/%u", buf, blacklists6[i].bits);
+ }
+ fputc('\n', sdc);
+ fclose(sdc);
+@@ -625,12 +1071,14 @@ configure_spamd(u_short dport, char *nam
+
+
+ int
+-configure_pf(struct cidr *blacklists)
++configure_pf(struct cidr *blacklists, u_int count, struct cidr *blacklists6,
++ u_int count6)
+ {
+ char *argv[9]= {"pfctl", "-q", "-t", "spamd", "-T", "replace",
+ "-f" "-", NULL};
++ char buf[INET6_ADDRSTRLEN];
+ static FILE *pf = NULL;
+- int pdes[2];
++ int pdes[2], i;
+
+ if (pf == NULL) {
+ if (pipe(pdes) != 0)
+@@ -659,10 +1107,13 @@ configure_pf(struct cidr *blacklists)
+ return (-1);
+ }
+ }
+- while (blacklists->addr != 0) {
+- fprintf(pf, "%s/%u\n", atop(blacklists->addr),
+- blacklists->bits);
+- blacklists++;
++ for (i = 0; i < count; i++) {
++ fprintf(pf, "%s/%u\n", atop(blacklists[i].addr4),
++ blacklists[i].bits);
++ }
++ for (i = 0; i < count; i++) {
++ inet_ntop(AF_INET6, &blacklists6[i].addr6, buf, sizeof(buf));
++ fprintf(pf, "%s/%u\n", buf, blacklists6[i].bits);
+ }
+ return (0);
+ }
+@@ -672,9 +1123,10 @@ getlist(char ** db_array, char *name, st
+ struct blacklist *blistnew)
+ {
+ char *buf, *method, *file, *message;
+- int fd, black = 0, serror;
+- size_t blc, bls;
++ int fd, black = 0, serror, ret;
++ size_t blc, bls, blc6, bls6;
+ struct bl *bl = NULL;
++ struct bl *bl6 = NULL;
+ gzFile gzf;
+
+ if (cgetent(&buf, db_array, name) != 0)
+@@ -686,12 +1138,18 @@ getlist(char ** db_array, char *name, st
+ blc = blistnew->blc;
+ bls = blistnew->bls;
+ bl = blistnew->bl;
++ blc6 = blistnew->blc6;
++ bls6 = blistnew->bls6;
++ bl6 = blistnew->bl6;
+ } else if (cgetcap(buf, "white", ':') != NULL) {
+ /* apply to most recent blacklist */
+ black = 0;
+ blc = blist->blc;
+ bls = blist->bls;
+ bl = blist->bl;
++ blc6 = blist->blc6;
++ bls6 = blist->bls6;
++ bl6 = blist->bl6;
+ } else
+ errx(1, "Must have \"black\" or \"white\" in %s", name);
+
+@@ -730,10 +1188,10 @@ getlist(char ** db_array, char *name, st
+ errx(1, "gzdopen");
+ }
+ free(buf);
+- bl = add_blacklist(bl, &blc, &bls, gzf, !black);
++ ret = add_blacklist(&bl, &blc, &bls, &bl6, &blc6, &bls6, gzf, !black);
+ serror = errno;
+ gzclose(gzf);
+- if (bl == NULL) {
++ if (!ret) {
+ errno = serror;
+ warn("Could not add %slist %s", black ? "black" : "white",
+ name);
+@@ -742,13 +1200,16 @@ getlist(char ** db_array, char *name, st
+ if (black) {
+ if (debug)
+ fprintf(stderr, "blacklist %s %zu entries\n",
+- name, blc / 2);
++ name, ((blc / 2) + (blc6 / 2)));
+ blistnew->message = message;
+ blistnew->name = name;
+ blistnew->black = black;
+ blistnew->bl = bl;
+ blistnew->blc = blc;
+ blistnew->bls = bls;
++ blistnew->bl6 = bl6;
++ blistnew->blc6 = blc6;
++ blistnew->bls6 = bls6;
+ } else {
+ /* whitelist applied to last active blacklist */
+ if (debug)
+@@ -757,6 +1218,9 @@ getlist(char ** db_array, char *name, st
+ blist->bl = bl;
+ blist->blc = blc;
+ blist->bls = bls;
++ blist->bl6 = bl6;
++ blist->blc6 = blc6;
++ blist->bls6 = bls6;
+ }
+ return (black);
+ }
+@@ -764,22 +1228,24 @@ getlist(char ** db_array, char *name, st
+ void
+ send_blacklist(struct blacklist *blist, in_port_t port)
+ {
+- struct cidr *cidrs;
+- u_int clc;
++ struct cidr *cidrs = NULL, *cidrs6 = NULL;
++ u_int clc = 0, clc6 = 0;
+
+- if (blist->blc > 0) {
+- cidrs = collapse_blacklist(blist->bl, blist->blc, &clc);
+- if (cidrs == NULL)
++ if (blist->blc > 0 || blist->blc6 > 0) {
++ if (!collapse_blacklist(blist->bl, blist->blc, blist->bl6,
++ blist->blc6, &cidrs, &clc, &cidrs6, &clc6))
+ err(1, NULL);
+ if (!dryrun) {
+ if (configure_spamd(port, blist->name,
+- blist->message, cidrs, clc) == -1)
++ blist->message, cidrs, clc, cidrs6, clc6) == -1)
+ err(1, "Can't connect to spamd on port %d",
+ port);
+- if (!greyonly && configure_pf(cidrs) == -1)
++ if (!greyonly && configure_pf(cidrs, clc, cidrs6, clc6)
++ == -1)
+ err(1, "pfctl failed");
+ }
+ free(cidrs);
++ free(cidrs6);
+ free(blist->bl);
+ }
+ }
diff --git a/spamd-setup.patch_1 b/spamd-setup.patch_1
new file mode 100644
index 0000000..08b37cc
--- /dev/null
+++ b/spamd-setup.patch_1
@@ -0,0 +1,939 @@
+Index: spamd-setup.c
+===================================================================
+RCS file: /cvs/src/libexec/spamd-setup/spamd-setup.c,v
+retrieving revision 1.47
+diff -u -p -u -r1.47 spamd-setup.c
+--- spamd-setup.c 12 Dec 2015 20:09:28 -0000 1.47
++++ spamd-setup.c 10 Jan 2016 19:23:12 -0000
+@@ -30,6 +30,7 @@
+ #include <errno.h>
+ #include <fcntl.h>
+ #include <stdio.h>
++#include <stdint.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <unistd.h>
+@@ -43,12 +44,20 @@
+ #define SPAMD_ARG_MAX 256 /* max # of args to an exec */
+
+ struct cidr {
+- u_int32_t addr;
++ sa_family_t ai_family;
++ union {
++ u_int32_t addr4;
++ u_int8_t addr6[16];
++ } addr;
+ u_int8_t bits;
+ };
+
+ struct bl {
+- u_int32_t addr;
++ sa_family_t ai_family;
++ union {
++ u_int32_t addr4;
++ u_int8_t addr6[16];
++ } addr;
+ int8_t b;
+ int8_t w;
+ };
+@@ -58,6 +67,8 @@ struct blacklist {
+ char *message;
+ struct bl *bl;
+ size_t blc, bls;
++ struct bl *bl6;
++ size_t blc6, bls6;
+ u_int8_t black;
+ };
+
+@@ -66,19 +77,30 @@ u_int8_t maxblock(u_int32_t, u_int8_t);
+ u_int8_t maxdiff(u_int32_t, u_int32_t);
+ struct cidr *range2cidrlist(struct cidr *, u_int *, u_int *, u_int32_t,
+ u_int32_t);
++struct cidr *range2cidrlist6(struct cidr *, u_int *, u_int *, u_int8_t *,
++ u_int8_t *);
+ void cidr2range(struct cidr, u_int32_t *, u_int32_t *);
++void cidr2range6(struct cidr, u_int8_t *, u_int8_t *);
+ char *atop(u_int32_t);
+-int parse_netblock(char *, struct bl *, struct bl *, int);
++int parse_netblock(char *, struct bl *, struct bl *, struct bl*,
++ struct bl *, int);
+ int open_child(char *, char **);
+ int fileget(char *);
+ int open_file(char *, char *);
+ char *fix_quoted_colons(char *);
+ void do_message(FILE *, char *);
+-struct bl *add_blacklist(struct bl *, size_t *, size_t *, gzFile, int);
++int add_blacklist(struct bl **, size_t *, size_t *, struct bl **,
++ size_t *, size_t *, gzFile, int);
++int cmp_ipv6(const u_int8_t *, const u_int8_t *);
+ int cmpbl(const void *, const void *);
+-struct cidr *collapse_blacklist(struct bl *, size_t, u_int *);
+-int configure_spamd(u_short, char *, char *, struct cidr *, u_int);
+-int configure_pf(struct cidr *);
++int cmpbl6(const void *, const void *);
++int collapse_blacklist(struct bl *, size_t, struct bl *, size_t,
++ struct cidr **, u_int *, struct cidr **, u_int *);
++struct cidr *collapse_blacklist4(struct bl *, size_t, u_int *);
++struct cidr *collapse_blacklist6(struct bl *, size_t, u_int *);
++int configure_spamd(u_short, char *, char *, struct cidr *, u_int,
++ struct cidr *, u_int);
++int configure_pf(struct cidr *, u_int, struct cidr *, u_int);
+ int getlist(char **, char *, struct blacklist *, struct blacklist *);
+ __dead void usage(void);
+
+@@ -89,6 +111,74 @@ int greyonly = 1;
+ extern char *__progname;
+
+ #define MAXIMUM(a,b) (((a)>(b))?(a):(b))
++#define MINIMUM(a,b) (((a)<(b))?(a):(b))
++
++void
++ipv6_add(u_int8_t *addr, u_int8_t num)
++{
++ int i;
++ u_int8_t tmp;
++
++ for (i = 15; i >= 0; i--) {
++ if (num == 0)
++ break;
++
++ if (num > (UINT8_MAX - addr[i]))
++ tmp = 1;
++ else
++ tmp = 0;
++
++ addr[i] += num;
++
++ num = tmp;
++ }
++}
++
++void
++ipv6_add2(u_int8_t *addr, u_int8_t *src)
++{
++ int i, j;
++ u_int8_t tmp, num;
++
++ for (i = 15; i >= 0; i--) {
++ num = src[i];
++
++ for (j = i; j >= 0; j--) {
++ if (num == 0)
++ break;
++
++ if (num > (UINT8_MAX - addr[j]))
++ tmp = 1;
++ else
++ tmp = 0;
++
++ addr[j] += num;
++
++ num = tmp;
++ }
++ }
++}
++
++void
++ipv6_sub(u_int8_t *addr, u_int8_t num)
++{
++ int i;
++ u_int8_t tmp;
++
++ for (i = 15; i >= 0; i--) {
++ if (num == 0)
++ break;
++
++ if (num > addr[i])
++ tmp = 1;
++ else
++ tmp = 0;
++
++ addr[i] -= num;
++
++ num = tmp;
++ }
++}
+
+ u_int32_t
+ imask(u_int8_t b)
+@@ -98,6 +188,24 @@ imask(u_int8_t b)
+ return (0xffffffffU << (32 - b));
+ }
+
++void
++imask6(u_int8_t *m, u_int8_t b)
++{
++ int i;
++ u_int8_t tmp;
++ memset(m, 0, sizeof(u_int8_t) * 16);
++
++ for (i = 0; i < 16; i++) {
++ if (b == 0)
++ break;
++
++ tmp = MINIMUM(b, 8);
++ b -= tmp;
++
++ m[i] = (0xffU << (8 - tmp));
++ }
++}
++
+ u_int8_t
+ maxblock(u_int32_t addr, u_int8_t bits)
+ {
+@@ -114,6 +222,26 @@ maxblock(u_int32_t addr, u_int8_t bits)
+ }
+
+ u_int8_t
++maxblock6(u_int8_t *addr, u_int8_t bits)
++{
++ u_int8_t m[16];
++ int i;
++
++ while (bits > 0) {
++ imask6(m, bits - 1);
++
++ for (i = 0; i < 16; i++) {
++ if ((addr[i] & m[i]) != addr[i]) {
++ return bits;
++ }
++ }
++
++ bits--;
++ }
++ return (bits);
++}
++
++u_int8_t
+ maxdiff(u_int32_t a, u_int32_t b)
+ {
+ u_int8_t bits = 0;
+@@ -130,6 +258,28 @@ maxdiff(u_int32_t a, u_int32_t b)
+ return (bits);
+ }
+
++u_int8_t
++maxdiff6(u_int8_t *a, u_int8_t *b)
++{
++ u_int8_t bits = 0;
++ u_int8_t m[16], tmp[16];
++ int i;
++
++ memcpy(tmp, b, sizeof(tmp));
++
++ ipv6_add(tmp, 1);
++ while (bits < 128) {
++ imask6(m, bits);
++
++ for (i = 0; i < 15; i++) {
++ if ((a[i] & m[i]) != (tmp[i] & m[i]))
++ return (bits);
++ }
++ bits++;
++ }
++ return (bits);
++}
++
+ struct cidr *
+ range2cidrlist(struct cidr *list, u_int *cli, u_int *cls, u_int32_t start,
+ u_int32_t end)
+@@ -150,7 +300,7 @@ range2cidrlist(struct cidr *list, u_int
+ list = tmp;
+ *cls += 32;
+ }
+- list[*cli].addr = start;
++ list[*cli].addr.addr4 = start;
+ list[*cli].bits = maxsize;
+ (*cli)++;
+ start = start + (1 << (32 - maxsize));
+@@ -158,11 +308,102 @@ range2cidrlist(struct cidr *list, u_int
+ return (list);
+ }
+
++struct cidr *
++range2cidrlist6(struct cidr *list, u_int *cli, u_int *cls, u_int8_t *start,
++ u_int8_t *end)
++{
++ u_int8_t maxsize, diff, btmp;
++ struct cidr *tmp;
++ int i;
++
++ while (cmp_ipv6(end, start) >= 0) {
++ /*{ // XXX
++ char s[INET6_ADDRSTRLEN], e[INET6_ADDRSTRLEN];
++
++ inet_ntop(AF_INET6, start, s, sizeof(s));
++ inet_ntop(AF_INET6, end, e, sizeof(e));
++
++ fprintf(stderr, "DEBUG: s=%s, e=%s\n", s, e);
++ }*/
++ maxsize = maxblock6(start, 128);
++ diff = maxdiff6(start, end);
++
++ maxsize = MAXIMUM(maxsize, diff);
++ if (*cls <= *cli + 1) {
++ tmp = reallocarray(list, *cls + 32,
++ sizeof(struct cidr));
++ if (tmp == NULL)
++ err(1, NULL);
++ list = tmp;
++ *cls += 32;
++ }
++ memcpy(list[*cli].addr.addr6, start,
++ sizeof(list[*cli].addr.addr6));
++ list[*cli].bits = maxsize;
++ (*cli)++;
++
++ maxsize = 128 - maxsize;
++ u_int8_t mask[16];
++ memset(mask, 0, sizeof(mask));
++
++ for (i = 15; i >= 0; i--) {
++ btmp = ((maxsize < 8) ? maxsize : 8);
++ maxsize -= btmp;
++
++ mask[i] = ((1 << btmp) - 1);
++
++ if (maxsize == 0)
++ break;
++ }
++
++ ipv6_add(mask, 1);
++ ipv6_add2(start, mask);
++
++ /* { // XXX
++ char m[INET6_ADDRSTRLEN];
++
++ inet_ntop(AF_INET6, mask, m, sizeof(m));
++
++ fprintf(stderr, "DEBUG: m=%s\n", m);
++ }*/
++ }
++
++ return (list);
++}
++
+ void
+ cidr2range(struct cidr cidr, u_int32_t *start, u_int32_t *end)
+ {
+- *start = cidr.addr;
+- *end = cidr.addr + (1 << (32 - cidr.bits)) - 1;
++ *start = cidr.addr.addr4;
++ *end = cidr.addr.addr4 + (1 << (32 - cidr.bits)) - 1;
++}
++
++void
++cidr2range6(struct cidr cidr, u_int8_t *start, u_int8_t *end)
++{
++ int i;
++ u_int8_t tmp, bits = 128 - cidr.bits;
++ u_int8_t mask[16];
++ memset(mask, 0, sizeof(mask));
++
++ for (i = 0; i < 16; i++)
++ end[i] = start[i] = cidr.addr.addr6[i];
++ //start[i] = cidr.addr.addr6[i];
++
++ for (i = 15; i >= 0; i--) {
++ if (bits == 0) {
++ //end[i] = start[i];
++ break;
++ } else {
++ tmp = MINIMUM(bits, 8);
++ bits -= tmp;
++
++ //end[i] = start[i] + ((1 << tmp) - 1); // XXX
++ mask[i] = ((1 << tmp) - 1);
++ }
++ }
++
++ ipv6_add2(end, mask);
+ }
+
+ char *
+@@ -176,11 +417,13 @@ atop(u_int32_t addr)
+ }
+
+ int
+-parse_netblock(char *buf, struct bl *start, struct bl *end, int white)
++parse_netblock(char *buf, struct bl *start, struct bl *end, struct bl *start6,
++ struct bl *end6, int white)
+ {
+- char astring[16], astring2[16];
++ char astring[40], astring2[40];
+ unsigned maskbits;
+ struct cidr c;
++ int ret = 0;
+
+ /* skip leading spaces */
+ while (*buf == ' ')
+@@ -189,56 +432,130 @@ parse_netblock(char *buf, struct bl *sta
+ if (*buf == '#')
+ return (0);
+ /* otherwise, look for a netblock of some sort */
+- if (sscanf(buf, "%15[^/]/%u", astring, &maskbits) == 2) {
+- /* looks like a cidr */
++ if (sscanf(buf, "%15[0123456789.]/%u", astring, &maskbits) == 2) {
++ /* looks like IPv4 cidr */
+ memset(&c.addr, 0, sizeof(c.addr));
+- if (inet_net_pton(AF_INET, astring, &c.addr, sizeof(c.addr))
+- == -1)
++ if (inet_net_pton(AF_INET, astring, &c.addr.addr4,
++ sizeof(c.addr.addr4)) == -1)
+ return (0);
+- c.addr = ntohl(c.addr);
++ c.ai_family = AF_INET;
++ c.addr.addr4 = ntohl(c.addr.addr4);
+ if (maskbits > 32)
+ return (0);
+ c.bits = maskbits;
+- cidr2range(c, &start->addr, &end->addr);
+- end->addr += 1;
++ cidr2range(c, &start->addr.addr4, &end->addr.addr4);
++ start->ai_family = end->ai_family = AF_INET;
++ end->addr.addr4 += 1;
++
++ ret = AF_INET;
++ } else if (sscanf(buf, "%39[0123456789ABCDEFabcdef:]/%u",
++ astring, &maskbits) == 2) {
++ /* looks like IPv6 cidr */
++ memset(&c.addr, 0, sizeof(c.addr));
++ if (inet_net_pton(AF_INET6, astring, &c.addr.addr6,
++ sizeof(c.addr.addr6)) == -1)
++ return (0);
++ c.ai_family = AF_INET6;
++ if (maskbits > 128)
++ return (0);
++ c.bits = maskbits;
++ cidr2range6(c, start6->addr.addr6, end6->addr.addr6);
++ start6->ai_family = end6->ai_family = AF_INET6;
++ ipv6_add(end6->addr.addr6, 1);
++
++ ret = AF_INET6;
+ } else if (sscanf(buf, "%15[0123456789.]%*[ -]%15[0123456789.]",
+ astring, astring2) == 2) {
+- /* looks like start - end */
++ /* looks like IPv4 start - end */
+ memset(&start->addr, 0, sizeof(start->addr));
+ memset(&end->addr, 0, sizeof(end->addr));
+- if (inet_net_pton(AF_INET, astring, &start->addr,
+- sizeof(start->addr)) == -1)
++ if (inet_net_pton(AF_INET, astring, &start->addr.addr4,
++ sizeof(start->addr.addr4)) == -1)
+ return (0);
+- start->addr = ntohl(start->addr);
+- if (inet_net_pton(AF_INET, astring2, &end->addr,
+- sizeof(end->addr)) == -1)
++ start->ai_family = AF_INET;
++ start->addr.addr4 = ntohl(start->addr.addr4);
++ if (inet_net_pton(AF_INET, astring2, &end->addr.addr4,
++ sizeof(end->addr.addr4)) == -1)
+ return (0);
+- end->addr = ntohl(end->addr) + 1;
++ end->ai_family = AF_INET;
++ end->addr.addr4 = ntohl(end->addr.addr4) + 1;
+ if (start > end)
+ return (0);
++
++ ret = AF_INET;
++ } else if (sscanf(buf, "%39[0123456789ABCDEFabcdef:]%*[ -]"
++ "%39[0123456789ABCDEFabcdef:]", astring, astring2) == 2) {
++ /* loks like IPv6 start - end */
++ memset(&start6->addr, 0, sizeof(start6->addr));
++ memset(&end6->addr, 0, sizeof(end6->addr));
++ if (inet_net_pton(AF_INET6, astring, &start6->addr.addr6,
++ sizeof(start6->addr.addr6)) == -1)
++ return (0);
++ start6->ai_family = AF_INET6;
++ if (inet_net_pton(AF_INET6, astring2, &end6->addr.addr6,
++ sizeof(end6->addr.addr6)) == -1)
++ return (0);
++ end6->ai_family = AF_INET6;
++ ipv6_add(end6->addr.addr6, 1);
++ if (start6 > end6)
++ return (0);
++
++ ret = AF_INET6;
+ } else if (sscanf(buf, "%15[0123456789.]", astring) == 1) {
+- /* just a single address */
++ /* just a single IPv4 address */
+ memset(&start->addr, 0, sizeof(start->addr));
+- if (inet_net_pton(AF_INET, astring, &start->addr,
+- sizeof(start->addr)) == -1)
++ if (inet_net_pton(AF_INET, astring, &start->addr.addr4,
++ sizeof(start->addr.addr4)) == -1)
+ return (0);
+- start->addr = ntohl(start->addr);
+- end->addr = start->addr + 1;
++ start->ai_family = AF_INET;
++ end->ai_family = AF_INET;
++ start->addr.addr4 = ntohl(start->addr.addr4);
++ end->addr.addr4 = start->addr.addr4 + 1;
++
++ ret = AF_INET;
++ } else if (sscanf(buf, "%39[0123456789ABCDEFabcdef:]", astring) == 1) {
++ /* just a single IPv6 address */
++ memset(&start6->addr, 0, sizeof(start6->addr));
++ if (inet_net_pton(AF_INET6, astring, &start6->addr.addr6,
++ sizeof(start6->addr.addr6)) == -1)
++ return (0);
++ start6->ai_family = AF_INET6;
++ end6->ai_family = AF_INET6;
++ memcpy(&end6->addr.addr6, &start6->addr.addr6,
++ sizeof(start6->addr.addr6));
++
++ ipv6_add(end6->addr.addr6, 1);
++
++ ret = AF_INET6;
+ } else
+ return (0);
+
+ if (white) {
+- start->b = 0;
+- start->w = 1;
+- end->b = 0;
+- end->w = -1;
++ if (ret == AF_INET) {
++ start->b = 0;
++ start->w = 1;
++ end->b = 0;
++ end->w = -1;
++ } else {
++ start6->b = 0;
++ start6->w = 1;
++ end->b = 0;
++ end->w = -1;
++ }
+ } else {
+- start->b = 1;
+- start->w = 0;
+- end->b = -1;
+- end->w = 0;
++ if (ret == AF_INET) {
++ start->b = 1;
++ start->w = 0;
++ end->b = -1;
++ end->w = 0;
++ } else {
++ start6->b = 1;
++ start6->w = 0;
++ end6->b = -1;
++ end6->w = 0;
++ }
+ }
+- return (1);
++ return (ret);
+ }
+
+ int
+@@ -445,8 +762,9 @@ do_message(FILE *sdc, char *msg)
+ }
+
+ /* retrieve a list from fd. add to blacklist bl */
+-struct bl *
+-add_blacklist(struct bl *bl, size_t *blc, size_t *bls, gzFile gzf, int white)
++int
++add_blacklist(struct bl **bl, size_t *blc, size_t *bls, struct bl **bl6,
++ size_t *blc6, size_t *bls6, gzFile gzf, int white)
+ {
+ int i, n, start, bu = 0, bs = 0, serrno = 0;
+ char *buf = NULL, *tmp;
+@@ -481,30 +799,55 @@ add_blacklist(struct bl *bl, size_t *blc
+ /* we assume that there is an IP for every 14 bytes */
+ if (*blc + bu / 7 >= *bls) {
+ *bls += bu / 7;
+- blt = reallocarray(bl, *bls, sizeof(struct bl));
++ blt = reallocarray(*bl, *bls, sizeof(struct bl));
+ if (blt == NULL) {
+ *bls -= bu / 7;
+ serrno = errno;
+ goto bldone;
+ }
+- bl = blt;
++ *bl = blt;
++ }
++ if (*blc6 + bu / 7 >= *bls6) {
++ *bls6 += bu / 7;
++ blt = reallocarray(*bl6, *bls6, sizeof(struct bl));
++ if (blt == NULL) {
++ *bls6 -= bu / 7;
++ serrno = errno;
++ goto bldone;
++ }
++ *bl6 = blt;
+ }
+ for (i = 0; i <= bu; i++) {
+ if (*blc + 1 >= *bls) {
+ *bls += 1024;
+- blt = reallocarray(bl, *bls, sizeof(struct bl));
++ blt = reallocarray(*bl, *bls, sizeof(struct bl));
+ if (blt == NULL) {
+ *bls -= 1024;
+ serrno = errno;
+ goto bldone;
+ }
+- bl = blt;
++ *bl = blt;
++ }
++ if (*blc6 + 1 >= *bls6) {
++ *bls6 += 1024;
++ blt = reallocarray(*bl6, *bls6, sizeof(struct bl));
++ if (blt == NULL) {
++ *bls6 -= 1024;
++ serrno = errno;
++ goto bldone;
++ }
++ *bl6 = blt;
+ }
+ if (i == bu || buf[i] == '\n') {
+ buf[i] = '\0';
+- if (parse_netblock(buf + start,
+- bl + *blc, bl + *blc + 1, white))
++ n = parse_netblock(buf + start,
++ *bl + *blc, *bl + *blc + 1,
++ *bl6 + *blc6, *bl6 + *blc6 + 1, white);
++
++ if (n == AF_INET)
+ *blc += 2;
++ else if (n == AF_INET6)
++ *blc6 += 2;
+ start = i + 1;
+ }
+ }
+@@ -512,29 +855,78 @@ add_blacklist(struct bl *bl, size_t *blc
+ errno = EIO;
+ bldone:
+ free(buf);
+- if (serrno)
++ if (serrno) {
+ errno = serrno;
+- return (bl);
++ return (0);
++ }
++ return (1);;
++}
++
++int
++cmp_ipv6(const u_int8_t *a, const u_int8_t *b)
++{
++ int i;
++ for (i = 0; i < 16; i++) {
++ if (a[i] > b[i])
++ return (1);
++ if (a[i] < b[i])
++ return (-1);
++ }
++ return (0);
+ }
+
+ int
+ cmpbl(const void *a, const void *b)
+ {
+- if (((struct bl *)a)->addr > ((struct bl *) b)->addr)
++ if (((struct bl *)a)->addr.addr4 > ((struct bl *) b)->addr.addr4)
+ return (1);
+- if (((struct bl *)a)->addr < ((struct bl *) b)->addr)
++ if (((struct bl *)a)->addr.addr4 < ((struct bl *) b)->addr.addr4)
+ return (-1);
+ return (0);
+ }
+
++int
++cmpbl6(const void *a, const void *b)
++{
++ u_int8_t *addr1, *addr2;
++
++ addr1 = ((struct bl *)a)->addr.addr6;
++ addr2 = ((struct bl *)b)->addr.addr6;
++
++ return cmp_ipv6(addr1, addr2);
++}
++
+ /*
+ * collapse_blacklist takes blacklist/whitelist entries sorts, removes
+ * overlaps and whitelist portions, and returns netblocks to blacklist
+ * as lists of nonoverlapping cidr blocks suitable for feeding in
+ * printable form to pfctl or spamd.
+ */
++
++int
++collapse_blacklist(struct bl *bl, size_t blc, struct bl *bl6, size_t blc6,
++ struct cidr **cidr, u_int *clc, struct cidr **cidr6, u_int *clc6)
++{
++ if (blc == 0 && blc6 == 0)
++ return (0);
++
++ if (blc > 0) {
++ *cidr = collapse_blacklist4(bl, blc, clc);
++ if (*cidr == NULL)
++ return (0);
++ }
++
++ if (blc6 > 0) {
++ *cidr6 = collapse_blacklist6(bl6, blc6, clc6);
++ if (*cidr6 == NULL)
++ return (0);
++ }
++
++ return (1);
++}
++
+ struct cidr *
+-collapse_blacklist(struct bl *bl, size_t blc, u_int *clc)
++collapse_blacklist4(struct bl *bl, size_t blc, u_int *clc)
+ {
+ int bs = 0, ws = 0, state=0;
+ u_int cli, cls, i;
+@@ -558,13 +950,13 @@ collapse_blacklist(struct bl *bl, size_t
+ qsort(bl, blc, sizeof(struct bl), cmpbl);
+ for (i = 0; i < blc;) {
+ laststate = state;
+- addr = bl[i].addr;
++ addr = bl[i].addr.addr4;
+
+ do {
+ bs += bl[i].b;
+ ws += bl[i].w;
+ i++;
+- } while (bl[i].addr == addr);
++ } while (bl[i].addr.addr4 == addr);
+ if (state == 1 && bs == 0)
+ state = 0;
+ else if (state == 0 && bs > 0)
+@@ -581,18 +973,74 @@ collapse_blacklist(struct bl *bl, size_t
+ }
+ laststate = state;
+ }
+- cl[cli].addr = 0;
++ cl[cli].addr.addr4 = 0;
+ *clc = cli;
+ return (cl);
+ }
+
++struct cidr *
++collapse_blacklist6(struct bl *bl6, size_t blc6, u_int *clc6)
++{
++ int bs = 0, ws = 0, state = 0;
++ u_int cli, cls, i;
++ struct cidr *cl;
++ int laststate;
++ u_int8_t bstart[16];
++ u_int8_t addr[16];
++
++ memset(bstart, 0, sizeof(bstart));
++
++ if (blc6 == 0)
++ return (NULL);
++
++ cli = 0;
++ cls = (blc6 / 2) + (blc6 / 20) + 1;
++ cl = reallocarray(NULL, cls, sizeof(struct cidr));
++ if (cl == NULL)
++ return (NULL);
++ qsort(bl6, blc6, sizeof(struct bl), cmpbl6);
++
++ for (i = 0; i < blc6;) {
++ laststate = state;
++ memcpy(addr, bl6[i].addr.addr6, sizeof(addr));
++
++ do {
++ bs += bl6[i].b;
++ ws += bl6[i].w;
++ i++;
++ } while (cmp_ipv6(bl6[i].addr.addr6, addr) == 0);
++ if (state == 1 && bs == 0)
++ state = 0;
++ else if (state == 0 && bs > 0)
++ state = 1;
++ if (ws > 0)
++ state = 0;
++ if (laststate == 0 && state == 1) {
++ /* start blacklist */
++ memcpy(bstart, addr, sizeof(addr));
++ }
++ if (laststate == 1 && state == 0) {
++ /* end blacklist */
++ ipv6_sub(addr, 1);
++ cl = range2cidrlist6(cl, &cli, &cls, bstart, addr);
++ }
++ laststate = state;
++ }
++
++ memset(&cl[cli].addr.addr6, 0, sizeof(cl[cli].addr.addr6));
++ *clc6 = cli;
++
++ return (cl);
++}
++
+ int
+ configure_spamd(u_short dport, char *name, char *message,
+- struct cidr *blacklists, u_int count)
++ struct cidr *blacklists, u_int count, struct cidr *blacklists6, u_int count6)
+ {
+- int lport = IPPORT_RESERVED - 1, s;
++ int lport = IPPORT_RESERVED - 1, s, i;
+ struct sockaddr_in sin;
+ FILE* sdc;
++ char buf[INET6_ADDRSTRLEN];
+
+ s = rresvport(&lport);
+ if (s == -1)
+@@ -612,10 +1060,14 @@ configure_spamd(u_short dport, char *nam
+ fputs(name, sdc);
+ do_message(sdc, message);
+ fprintf(sdc, ";inet;%u", count);
+- while (blacklists->addr != 0) {
+- fprintf(sdc, ";%s/%u", atop(blacklists->addr),
+- blacklists->bits);
+- blacklists++;
++ for (i = 0; i < count; i++) {
++ fprintf(sdc, ";%s/%u", atop(blacklists[i].addr.addr4),
++ blacklists[i].bits);
++ }
++ fprintf(sdc, ";inet6;%u", count6);
++ for (i = 0; i < count6; i++) {
++ inet_ntop(AF_INET6, blacklists6[i].addr.addr6, buf, sizeof(buf));
++ fprintf(sdc, ";%s/%u", buf, blacklists6[i].bits);
+ }
+ fputc('\n', sdc);
+ fclose(sdc);
+@@ -625,12 +1077,14 @@ configure_spamd(u_short dport, char *nam
+
+
+ int
+-configure_pf(struct cidr *blacklists)
++configure_pf(struct cidr *blacklists, u_int count, struct cidr *blacklists6,
++ u_int count6)
+ {
+ char *argv[9]= {"pfctl", "-q", "-t", "spamd", "-T", "replace",
+ "-f" "-", NULL};
++ char buf[INET6_ADDRSTRLEN];
+ static FILE *pf = NULL;
+- int pdes[2];
++ int pdes[2], i;
+
+ if (pf == NULL) {
+ if (pipe(pdes) != 0)
+@@ -659,10 +1113,13 @@ configure_pf(struct cidr *blacklists)
+ return (-1);
+ }
+ }
+- while (blacklists->addr != 0) {
+- fprintf(pf, "%s/%u\n", atop(blacklists->addr),
+- blacklists->bits);
+- blacklists++;
++ for (i = 0; i < count; i++) {
++ fprintf(pf, "%s/%u\n", atop(blacklists[i].addr.addr4),
++ blacklists[i].bits);
++ }
++ for (i = 0; i < count; i++) {
++ inet_ntop(AF_INET6, blacklists6[i].addr.addr6, buf, sizeof(buf));
++ fprintf(pf, "%s/%u\n", buf, blacklists6[i].bits);
+ }
+ return (0);
+ }
+@@ -672,9 +1129,10 @@ getlist(char ** db_array, char *name, st
+ struct blacklist *blistnew)
+ {
+ char *buf, *method, *file, *message;
+- int fd, black = 0, serror;
+- size_t blc, bls;
++ int fd, black = 0, serror, ret;
++ size_t blc, bls, blc6, bls6;
+ struct bl *bl = NULL;
++ struct bl *bl6 = NULL;
+ gzFile gzf;
+
+ if (cgetent(&buf, db_array, name) != 0)
+@@ -686,12 +1144,18 @@ getlist(char ** db_array, char *name, st
+ blc = blistnew->blc;
+ bls = blistnew->bls;
+ bl = blistnew->bl;
++ blc6 = blistnew->blc6;
++ bls6 = blistnew->bls6;
++ bl6 = blistnew->bl6;
+ } else if (cgetcap(buf, "white", ':') != NULL) {
+ /* apply to most recent blacklist */
+ black = 0;
+ blc = blist->blc;
+ bls = blist->bls;
+ bl = blist->bl;
++ blc6 = blist->blc6;
++ bls6 = blist->bls6;
++ bl6 = blist->bl6;
+ } else
+ errx(1, "Must have \"black\" or \"white\" in %s", name);
+
+@@ -730,10 +1194,10 @@ getlist(char ** db_array, char *name, st
+ errx(1, "gzdopen");
+ }
+ free(buf);
+- bl = add_blacklist(bl, &blc, &bls, gzf, !black);
++ ret = add_blacklist(&bl, &blc, &bls, &bl6, &blc6, &bls6, gzf, !black);
+ serror = errno;
+ gzclose(gzf);
+- if (bl == NULL) {
++ if (!ret) {
+ errno = serror;
+ warn("Could not add %slist %s", black ? "black" : "white",
+ name);
+@@ -742,13 +1206,16 @@ getlist(char ** db_array, char *name, st
+ if (black) {
+ if (debug)
+ fprintf(stderr, "blacklist %s %zu entries\n",
+- name, blc / 2);
++ name, ((blc / 2) + (blc6 / 2)));
+ blistnew->message = message;
+ blistnew->name = name;
+ blistnew->black = black;
+ blistnew->bl = bl;
+ blistnew->blc = blc;
+ blistnew->bls = bls;
++ blistnew->bl6 = bl6;
++ blistnew->blc6 = blc6;
++ blistnew->bls6 = bls6;
+ } else {
+ /* whitelist applied to last active blacklist */
+ if (debug)
+@@ -757,6 +1224,9 @@ getlist(char ** db_array, char *name, st
+ blist->bl = bl;
+ blist->blc = blc;
+ blist->bls = bls;
++ blist->bl6 = bl6;
++ blist->blc6 = blc6;
++ blist->bls6 = bls6;
+ }
+ return (black);
+ }
+@@ -764,22 +1234,24 @@ getlist(char ** db_array, char *name, st
+ void
+ send_blacklist(struct blacklist *blist, in_port_t port)
+ {
+- struct cidr *cidrs;
+- u_int clc;
++ struct cidr *cidrs = NULL, *cidrs6 = NULL;
++ u_int clc = 0, clc6 = 0;
+
+- if (blist->blc > 0) {
+- cidrs = collapse_blacklist(blist->bl, blist->blc, &clc);
+- if (cidrs == NULL)
++ if (blist->blc > 0 || blist->blc6 > 0) {
++ if (!collapse_blacklist(blist->bl, blist->blc, blist->bl6,
++ blist->blc6, &cidrs, &clc, &cidrs6, &clc6))
+ err(1, NULL);
+ if (!dryrun) {
+ if (configure_spamd(port, blist->name,
+- blist->message, cidrs, clc) == -1)
++ blist->message, cidrs, clc, cidrs6, clc6) == -1)
+ err(1, "Can't connect to spamd on port %d",
+ port);
+- if (!greyonly && configure_pf(cidrs) == -1)
++ if (!greyonly && configure_pf(cidrs, clc, cidrs6, clc6)
++ == -1)
+ err(1, "pfctl failed");
+ }
+ free(cidrs);
++ free(cidrs6);
+ free(blist->bl);
+ }
+ }
diff --git a/spamd.patch b/spamd.patch
new file mode 100644
index 0000000..3e7c986
--- /dev/null
+++ b/spamd.patch
@@ -0,0 +1,938 @@
+Index: grey.c
+===================================================================
+RCS file: /cvs/src/libexec/spamd/grey.c,v
+retrieving revision 1.62
+diff -u -r1.62 grey.c
+--- grey.c 10 Dec 2015 16:06:29 -0000 1.62
++++ grey.c 31 Dec 2015 20:25:24 -0000
+@@ -54,8 +54,10 @@
+ #define satosin(sa) ((struct sockaddr_in *)(sa))
+ #define satosin6(sa) ((struct sockaddr_in6 *)(sa))
+
+-void configure_spamd(char **, u_int, FILE *);
+-int configure_pf(char **, int);
++struct listentry;
++
++void configure_spamd(struct listentry *, u_int, FILE *);
++int configure_pf(struct listentry *, int);
+ char *dequotetolower(const char *);
+ void readsuffixlists(void);
+ void freeaddrlists(void);
+@@ -69,11 +71,10 @@
+ int greyreader(void);
+ void greyscanner(void);
+
+-
+ u_int whitecount, whitealloc;
+ u_int trapcount, trapalloc;
+-char **whitelist;
+-char **traplist;
++struct listentry *whitelist;
++struct listentry *traplist;
+
+ char *traplist_name = "spamd-greytrap";
+ char *traplist_msg = "\"Your address %A has mailed to spamtraps here\\n\"";
+@@ -89,6 +90,18 @@
+ int act;
+ };
+
++struct listentry {
++ char *entry;
++ int af;
++};
++
++struct address_list {
++ int count;
++ char *addrs;
++ size_t size;
++ size_t len;
++};
++
+ #define DBC_ADD 1
+ #define DBC_DEL 2
+
+@@ -124,6 +137,37 @@
+ _exit(1);
+ }
+
++static int
++add_address(struct address_list *rec, struct listentry *addr)
++{
++ size_t entry_len = strlen(addr->entry);
++ size_t extra_len = (addr->af == AF_INET6) ? 6 : 5; /* semicolon, prefix and nul */
++ if (rec->len + entry_len + extra_len >= rec->size) {
++ char *tmp = reallocarray(rec->addrs, rec->size + 1024,
++ sizeof(char));
++ if (tmp == NULL) {
++ syslog_r(LOG_ERR, &sdata,
++ "configure_spamd: malloc failed");
++ return(-1);
++ }
++ rec->addrs = tmp;
++ if (rec->size == 0)
++ rec->addrs[0] = '\0';
++ rec->size += 1024;
++ }
++
++ strlcat(rec->addrs, ";", rec->size);
++ strlcat(rec->addrs, addr->entry, rec->size);
++ if (addr->af == AF_INET6)
++ strlcat(rec->addrs, "/128", rec->size);
++ else
++ strlcat(rec->addrs, "/32", rec->size);
++ rec->len += entry_len;
++ rec->count++;
++
++ return(0);
++}
++
+ /*
+ * Greatly simplified version from spamd_setup.c - only
+ * sends one blacklist to an already open stream. Has no need
+@@ -131,24 +175,36 @@
+ * host hits.
+ */
+ void
+-configure_spamd(char **addrs, u_int count, FILE *sdc)
++configure_spamd(struct listentry *addrs, u_int count, FILE *sdc)
+ {
+ u_int i;
++ struct address_list inet = { 0, NULL, 0, 0 };
++ struct address_list inet6 = { 0, NULL, 0, 0 };
+
+- /* XXX - doesn't support IPV6 yet */
+ fprintf(sdc, "%s;", traplist_name);
+ if (count != 0) {
+- fprintf(sdc, "%s;inet;%u", traplist_msg, count);
+- for (i = 0; i < count; i++)
+- fprintf(sdc, ";%s/32", addrs[i]);
++ for (i = 0; i < count; i++) {
++ if (addrs[i].af == AF_INET6)
++ (void)add_address(&inet6, &addrs[i]);
++ else
++ (void)add_address(&inet, &addrs[i]);
++ }
++
++ fprintf(sdc, "%s", traplist_msg);
++ if (inet.count > 0)
++ fprintf(sdc, ";inet;%u%s", inet.count, inet.addrs);
++ if (inet6.count > 0)
++ fprintf(sdc, ";inet6;%u%s", inet6.count, inet6.addrs);
+ }
++ free(inet.addrs);
++ free(inet6.addrs);
+ fputc('\n', sdc);
+ if (fflush(sdc) == EOF)
+ syslog_r(LOG_DEBUG, &sdata, "configure_spamd: fflush failed (%m)");
+ }
+
+ int
+-configure_pf(char **addrs, int count)
++configure_pf(struct listentry *addrs, int count)
+ {
+ FILE *pf = NULL;
+ int i, pdes[2], status;
+@@ -210,8 +266,9 @@
+ return(-1);
+ }
+ for (i = 0; i < count; i++)
+- if (addrs[i] != NULL)
+- fprintf(pf, "%s/32\n", addrs[i]);
++ if (addrs[i].entry != NULL)
++ fprintf(pf, "%s/%s\n", addrs[i].entry,
++ (addrs[i].af == AF_INET6) ? "128" : "32");
+ fclose(pf);
+
+ waitpid(pid, &status, 0);
+@@ -305,14 +362,14 @@
+
+ if (whitelist != NULL)
+ for (i = 0; i < whitecount; i++) {
+- free(whitelist[i]);
+- whitelist[i] = NULL;
++ free(whitelist[i].entry);
++ whitelist[i].entry = NULL;
+ }
+ whitecount = 0;
+ if (traplist != NULL) {
+ for (i = 0; i < trapcount; i++) {
+- free(traplist[i]);
+- traplist[i] = NULL;
++ free(traplist[i].entry);
++ traplist[i].entry = NULL;
+ }
+ }
+ trapcount = 0;
+@@ -325,17 +382,17 @@
+ struct addrinfo hints, *res;
+
+ memset(&hints, 0, sizeof(hints));
+- hints.ai_family = AF_INET; /*for now*/
++ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ hints.ai_protocol = IPPROTO_UDP; /*dummy*/
+ hints.ai_flags = AI_NUMERICHOST;
+
+ if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
+ if (whitecount == whitealloc) {
+- char **tmp;
++ struct listentry *tmp;
+
+ tmp = reallocarray(whitelist,
+- whitealloc + 1024, sizeof(char *));
++ whitealloc + 1024, sizeof(struct listentry));
+ if (tmp == NULL) {
+ freeaddrinfo(res);
+ return(-1);
+@@ -343,11 +400,12 @@
+ whitelist = tmp;
+ whitealloc += 1024;
+ }
+- whitelist[whitecount] = strdup(addr);
+- if (whitelist[whitecount] == NULL) {
++ whitelist[whitecount].entry = strdup(addr);
++ if (whitelist[whitecount].entry == NULL) {
+ freeaddrinfo(res);
+ return(-1);
+ }
++ whitelist[whitecount].af = res->ai_family;
+ whitecount++;
+ freeaddrinfo(res);
+ } else
+@@ -362,17 +420,17 @@
+ struct addrinfo hints, *res;
+
+ memset(&hints, 0, sizeof(hints));
+- hints.ai_family = AF_INET; /*for now*/
++ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ hints.ai_protocol = IPPROTO_UDP; /*dummy*/
+ hints.ai_flags = AI_NUMERICHOST;
+
+ if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
+ if (trapcount == trapalloc) {
+- char **tmp;
++ struct listentry *tmp;
+
+ tmp = reallocarray(traplist,
+- trapalloc + 1024, sizeof(char *));
++ trapalloc + 1024, sizeof(struct listentry));
+ if (tmp == NULL) {
+ freeaddrinfo(res);
+ return(-1);
+@@ -380,11 +438,12 @@
+ traplist = tmp;
+ trapalloc += 1024;
+ }
+- traplist[trapcount] = strdup(addr);
+- if (traplist[trapcount] == NULL) {
++ traplist[trapcount].entry = strdup(addr);
++ if (traplist[trapcount].entry == NULL) {
+ freeaddrinfo(res);
+ return(-1);
+ }
++ traplist[trapcount].af = res->ai_family;
+ trapcount++;
+ freeaddrinfo(res);
+ } else
+@@ -719,6 +778,7 @@
+ gd.bcount++;
+ } else
+ gd.pcount++;
++ gd.expire = expire;
+ memset(&dbk, 0, sizeof(dbk));
+ dbk.size = strlen(ip);
+ dbk.data = ip;
+@@ -880,13 +940,13 @@
+ int
+ twread(char *buf)
+ {
+- if ((strncmp(buf, "WHITE:", 6) == 0) ||
+- (strncmp(buf, "TRAP:", 5) == 0)) {
++ if ((strncmp(buf, "WHITE;", 6) == 0) ||
++ (strncmp(buf, "TRAP;", 5) == 0)) {
+ char **ap, *argv[5];
+ int argc = 0;
+
+ for (ap = argv;
+- ap < &argv[4] && (*ap = strsep(&buf, ":")) != NULL;) {
++ ap < &argv[4] && (*ap = strsep(&buf, ";")) != NULL;) {
+ if (**ap != '\0')
+ ap++;
+ argc++;
+@@ -910,7 +970,7 @@
+ struct addrinfo hints, *res;
+
+ memset(&hints, 0, sizeof(hints));
+- hints.ai_family = AF_INET; /*for now*/
++ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ hints.ai_protocol = IPPROTO_UDP; /*dummy*/
+ hints.ai_flags = AI_NUMERICHOST;
+Index: spamd.c
+===================================================================
+RCS file: /cvs/src/libexec/spamd/spamd.c,v
+retrieving revision 1.137
+diff -u -r1.137 spamd.c
+--- spamd.c 12 Dec 2015 20:09:28 -0000 1.137
++++ spamd.c 31 Dec 2015 20:25:24 -0000
+@@ -56,8 +56,8 @@
+ int il;
+ struct sockaddr_storage ss;
+ void *ia;
+- char addr[32];
+- char caddr[32];
++ char addr[40];
++ char caddr[40];
+ char helo[MAX_MAIL], mail[MAX_MAIL], rcpt[MAX_MAIL];
+ struct sdlist **blacklists;
+ struct tls *cctx;
+@@ -113,6 +113,7 @@
+ int read_configline(FILE *);
+ void spamd_tls_init(void);
+ void check_spamd_db(void);
++void accept_connection(int, struct sockaddr *, socklen_t, struct con *);
+
+ char hostname[HOST_NAME_MAX+1];
+ struct syslog_data sdata = SYSLOG_DATA_INIT;
+@@ -709,9 +710,6 @@
+ time_t tt;
+ int error;
+
+- if (sa->sa_family != AF_INET)
+- errx(1, "not supported yet");
+-
+ time(&tt);
+ free(cp->obuf);
+ free(cp->blacklists);
+@@ -723,7 +721,10 @@
+ cp->pfd->fd = fd;
+ memcpy(&cp->ss, sa, sa->sa_len);
+ cp->af = sa->sa_family;
+- cp->ia = &((struct sockaddr_in *)&cp->ss)->sin_addr;
++ if (sa->sa_family == AF_INET)
++ cp->ia = &((struct sockaddr_in *)&cp->ss)->sin_addr;
++ else
++ cp->ia = &((struct sockaddr_in6 *)&cp->ss)->sin6_addr;
+ cp->blacklists = sdl_lookup(blacklists, cp->af, cp->ia);
+ cp->stutter = (greylist && !grey_stutter && cp->blacklists == NULL) ?
+ 0 : stutter;
+@@ -1208,13 +1209,54 @@
+ return(maxfiles - 200);
+ }
+
++void
++accept_connection(int sfd, struct sockaddr *sin,
++ socklen_t sinlen, struct con *con)
++{
++ int s2, i;
++
++ s2 = accept(sfd, sin, &sinlen);
++ if (s2 == -1) {
++ switch (errno) {
++ case EINTR:
++ case ECONNABORTED:
++ break;
++ case EMFILE:
++ case ENFILE:
++ slowdowntill = time(NULL) + 1;
++ break;
++ default:
++ errx(1, "accept");
++ }
++ } else {
++ /* Check if we hit the chosen fd limit */
++ for (i = 0; i < maxcon; i++)
++ if (con->pfd->fd == -1)
++ break;
++ if (i == maxcon) {
++ close(s2);
++ slowdowntill = 0;
++ } else {
++ initcon(&con[i], s2, sin);
++ syslog_r(LOG_INFO, &sdata,
++ "%s: connected (%d/%d)%s%s",
++ con[i].addr, clients, blackcount,
++ ((con[i].lists == NULL) ? "" :
++ ", lists:"),
++ ((con[i].lists == NULL) ? "":
++ con[i].lists));
++ }
++ }
++}
++
+ /* Symbolic indexes for pfd[] below */
+ #define PFD_SMTPLISTEN 0
+-#define PFD_CONFLISTEN 1
+-#define PFD_SYNCFD 2
+-#define PFD_CONFFD 3
+-#define PFD_TRAPFD 4
+-#define PFD_FIRSTCON 5
++#define PFD_SMTPLISTEN6 1
++#define PFD_CONFLISTEN 2
++#define PFD_SYNCFD 3
++#define PFD_CONFFD 4
++#define PFD_TRAPFD 5
++#define PFD_FIRSTCON 6
+
+ int
+ main(int argc, char *argv[])
+@@ -1222,15 +1264,24 @@
+ struct pollfd *pfd;
+ struct sockaddr_in sin;
+ struct sockaddr_in lin;
+- int ch, smtplisten, conflisten, syncfd = -1, i, one = 1;
++ struct sockaddr_in6 sin6;
++ int ch, smtplisten, smtplisten6, conflisten, syncfd = -1, i, one = 1;
+ u_short port;
+ long long passt, greyt, whitet;
+ struct servent *ent;
+ struct rlimit rlp;
+ char *bind_address = NULL;
++ char *bind6_address = NULL;
+ const char *errstr;
+ char *sync_iface = NULL;
+ char *sync_baddr = NULL;
++ struct addrinfo hints, *res;
++
++ memset(&hints, 0, sizeof(hints));
++ hints.ai_family = PF_UNSPEC;
++ hints.ai_socktype = SOCK_DGRAM; /* dummy */
++ hints.ai_protocol = IPPROTO_UDP; /* dummy */
++ hints.ai_flags = AI_NUMERICHOST;
+
+ tzset();
+ openlog_r("spamd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata);
+@@ -1262,7 +1313,13 @@
+ nreply = "550";
+ break;
+ case 'l':
+- bind_address = optarg;
++ if (getaddrinfo(optarg, NULL, &hints, &res) != 0)
++ errx(1, "-l %s: invalid address", optarg);
++ if (res->ai_family == AF_INET6)
++ bind6_address = optarg;
++ else
++ bind_address = optarg;
++ freeaddrinfo(res);
+ break;
+ case 'B':
+ maxblack = strtonum(optarg, 0, INT_MAX, &errstr);
+@@ -1413,6 +1470,14 @@
+ sizeof(one)) == -1)
+ return (-1);
+
++ smtplisten6 = socket(AF_INET6, SOCK_STREAM, 0);
++ if (smtplisten6 == -1)
++ err(1, "socket");
++
++ if (setsockopt(smtplisten6, SOL_SOCKET, SO_REUSEADDR, &one,
++ sizeof(sin6)) == -1)
++ return (-1);
++
+ conflisten = socket(AF_INET, SOCK_STREAM, 0);
+ if (conflisten == -1)
+ err(1, "socket");
+@@ -1434,6 +1499,19 @@
+ if (bind(smtplisten, (struct sockaddr *)&sin, sizeof sin) == -1)
+ err(1, "bind");
+
++ memset(&sin6, 0, sizeof sin6);
++ sin6.sin6_len = sizeof(sin6);
++ if (bind6_address) {
++ if (inet_pton(AF_INET6, bind6_address, &sin6.sin6_addr) != 1)
++ err(1, "inet_pton");
++ } else
++ sin6.sin6_addr = (struct in6_addr)IN6ADDR_LOOPBACK_INIT;
++ sin6.sin6_family = AF_INET6;
++ sin6.sin6_port = htons(port);
++
++ if (bind(smtplisten6, (struct sockaddr *)&sin6, sizeof sin6) == -1)
++ err(1, "bind");
++
+ memset(&lin, 0, sizeof sin);
+ lin.sin_len = sizeof(sin);
+ lin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+@@ -1532,6 +1610,9 @@
+ if (listen(smtplisten, 10) == -1)
+ err(1, "listen");
+
++ if (listen(smtplisten6, 10) == -1)
++ err(1, "listen");
++
+ if (listen(conflisten, 10) == -1)
+ err(1, "listen");
+
+@@ -1557,6 +1638,7 @@
+
+ /* events and pfd entries for con[] are filled in below. */
+ pfd[PFD_SMTPLISTEN].fd = smtplisten;
++ pfd[PFD_SMTPLISTEN6].fd = smtplisten6;
+ pfd[PFD_CONFLISTEN].fd = conflisten;
+
+ while (1) {
+@@ -1595,11 +1677,13 @@
+ numcon = i + 1;
+ }
+ pfd[PFD_SMTPLISTEN].events = 0;
++ pfd[PFD_SMTPLISTEN6].events = 0;
+ pfd[PFD_CONFLISTEN].events = 0;
+ pfd[PFD_CONFFD].events = 0;
+ pfd[PFD_CONFFD].fd = conffd;
+ if (slowdowntill == 0) {
+ pfd[PFD_SMTPLISTEN].events = POLLIN;
++ pfd[PFD_SMTPLISTEN6].events = POLLIN;
+
+ /* only one active config conn at a time */
+ if (conffd == -1)
+@@ -1647,45 +1731,12 @@
+ handlew(&con[i], clients + 5 < maxcon);
+ }
+ }
+- if (pfd[PFD_SMTPLISTEN].revents & (POLLIN|POLLHUP)) {
+- socklen_t sinlen;
+- int s2;
+-
+- sinlen = sizeof(sin);
+- s2 = accept(smtplisten, (struct sockaddr *)&sin, &sinlen);
+- if (s2 == -1) {
+- switch (errno) {
+- case EINTR:
+- case ECONNABORTED:
+- break;
+- case EMFILE:
+- case ENFILE:
+- slowdowntill = time(NULL) + 1;
+- break;
+- default:
+- errx(1, "accept");
+- }
+- } else {
+- /* Check if we hit the chosen fd limit */
+- for (i = 0; i < maxcon; i++)
+- if (con[i].pfd->fd == -1)
+- break;
+- if (i == maxcon) {
+- close(s2);
+- slowdowntill = 0;
+- } else {
+- initcon(&con[i], s2,
+- (struct sockaddr *)&sin);
+- syslog_r(LOG_INFO, &sdata,
+- "%s: connected (%d/%d)%s%s",
+- con[i].addr, clients, blackcount,
+- ((con[i].lists == NULL) ? "" :
+- ", lists:"),
+- ((con[i].lists == NULL) ? "":
+- con[i].lists));
+- }
+- }
+- }
++ if (pfd[PFD_SMTPLISTEN].revents & (POLLIN|POLLHUP))
++ accept_connection(smtplisten, (struct sockaddr *)&sin,
++ sizeof(sin), con);
++ if (pfd[PFD_SMTPLISTEN6].revents & (POLLIN|POLLHUP))
++ accept_connection(smtplisten6, (struct sockaddr *)&sin6,
++ sizeof(sin6), con);
+ if (pfd[PFD_CONFLISTEN].revents & (POLLIN|POLLHUP)) {
+ socklen_t sinlen;
+
+Index: sync.c
+===================================================================
+RCS file: /cvs/src/libexec/spamd/sync.c,v
+retrieving revision 1.11
+diff -u -r1.11 sync.c
+--- sync.c 23 Nov 2014 21:19:47 -0000 1.11
++++ sync.c 31 Dec 2015 20:25:24 -0000
+@@ -241,11 +241,15 @@
+ struct sockaddr_in addr;
+ struct spam_synctlv_hdr *tlv;
+ struct spam_synctlv_grey *sg;
++ struct spam_synctlv_grey6 *sg6;
+ struct spam_synctlv_addr *sd;
++ struct spam_synctlv_addr6 *sd6;
+ u_int8_t buf[SPAM_SYNC_MAXSIZE];
+ u_int8_t hmac[2][SPAM_SYNC_HMAC_LEN];
+ struct in_addr ip;
++ struct in6_addr ip6;
+ char *from, *to, *helo;
++ char ipstr[40] = { '\0' };
+ u_int8_t *p;
+ socklen_t addr_len;
+ ssize_t len;
+@@ -268,7 +272,7 @@
+ hdr = (struct spam_synchdr *)buf;
+ if (len < sizeof(struct spam_synchdr) ||
+ hdr->sh_version != SPAM_SYNC_VERSION ||
+- hdr->sh_af != AF_INET ||
++ (hdr->sh_af != AF_INET && hdr->sh_af != AF_INET6) ||
+ len < ntohs(hdr->sh_length))
+ goto trunc;
+ len = ntohs(hdr->sh_length);
+@@ -296,72 +300,120 @@
+
+ switch (ntohs(tlv->st_type)) {
+ case SPAM_SYNC_GREY:
+- sg = (struct spam_synctlv_grey *)tlv;
+- if ((sizeof(*sg) +
+- ntohs(sg->sg_from_length) +
+- ntohs(sg->sg_to_length) +
+- ntohs(sg->sg_helo_length)) >
+- ntohs(tlv->st_length))
+- goto trunc;
+-
+- ip.s_addr = sg->sg_ip;
+- from = (char *)(sg + 1);
+- to = from + ntohs(sg->sg_from_length);
+- helo = to + ntohs(sg->sg_to_length);
++ if (hdr->sh_af == AF_INET) {
++ sg = (struct spam_synctlv_grey *)tlv;
++ if ((sizeof(*sg) +
++ ntohs(sg->sg_from_length) +
++ ntohs(sg->sg_to_length) +
++ ntohs(sg->sg_helo_length)) >
++ ntohs(tlv->st_length))
++ goto trunc;
++
++ ip.s_addr = sg->sg_ip;
++ inet_ntop(AF_INET, &ip, ipstr, sizeof(ipstr));
++
++ from = (char *)(sg + 1);
++ to = from + ntohs(sg->sg_from_length);
++ helo = to + ntohs(sg->sg_to_length);
++ } else {
++ sg6 = (struct spam_synctlv_grey6 *)tlv;
++ if ((sizeof(*sg6)) +
++ ntohs(sg6->sg6_from_length) +
++ ntohs(sg6->sg6_to_length) +
++ ntohs(sg6->sg6_helo_length) >
++ ntohs(tlv->st_length))
++ goto trunc;
++
++ memcpy(&ip6.s6_addr, &sg6->sg6_ip, sizeof(ip6.s6_addr));
++ inet_ntop(AF_INET6, &ip6, ipstr, sizeof(ipstr));
++
++ from = (char *)(sg6 + 1);
++ to = from + ntohs(sg6->sg6_from_length);
++ helo = to + ntohs(sg6->sg6_to_length);
++ }
++
+ if (debug) {
+ fprintf(stderr, "%s(sync): "
+ "received grey entry ",
+ inet_ntoa(addr.sin_addr));
+ fprintf(stderr, "helo %s ip %s "
+ "from %s to %s\n",
+- helo, inet_ntoa(ip), from, to);
++ helo, ipstr, from, to);
+ }
+ if (greylist) {
+ /* send this info to the greylister */
+ fprintf(grey,
+ "SYNC\nHE:%s\nIP:%s\nFR:%s\nTO:%s\n",
+- helo, inet_ntoa(ip), from, to);
++ helo, ipstr, from, to);
+ fflush(grey);
+ }
+ break;
+ case SPAM_SYNC_WHITE:
+- sd = (struct spam_synctlv_addr *)tlv;
+- if (sizeof(*sd) != ntohs(tlv->st_length))
+- goto trunc;
++ if (hdr->sh_af == AF_INET) {
++ sd = (struct spam_synctlv_addr *)tlv;
++ if (sizeof(*sd) != ntohs(tlv->st_length))
++ goto trunc;
++
++ ip.s_addr = sd->sd_ip;
++ inet_ntop(AF_INET, &ip, ipstr, sizeof(ipstr));
++
++ expire = ntohl(sd->sd_expire);
++ } else {
++ sd6 = (struct spam_synctlv_addr6 *)tlv;
++ if (sizeof(*sd6) != ntohs(tlv->st_length))
++ goto trunc;
++
++ memcpy(&ip6.s6_addr, &sd6->sd6_ip, sizeof(ip6.s6_addr));
++ inet_ntop(AF_INET6, &ip6, ipstr, sizeof(ipstr));
++
++ expire = ntohl(sd6->sd6_expire);
++ }
+
+- ip.s_addr = sd->sd_ip;
+- expire = ntohl(sd->sd_expire);
+ if (debug) {
+ fprintf(stderr, "%s(sync): "
+ "received white entry ",
+ inet_ntoa(addr.sin_addr));
+- fprintf(stderr, "ip %s ", inet_ntoa(ip));
++ fprintf(stderr, "ip %s ", ipstr);
+ }
+ if (greylist) {
+ /* send this info to the greylister */
+- fprintf(grey, "WHITE:%s:", inet_ntoa(ip));
+- fprintf(grey, "%s:%u\n",
++ fprintf(grey, "WHITE;%s;", ipstr);
++ fprintf(grey, "%s;%u\n",
+ inet_ntoa(addr.sin_addr), expire);
+ fflush(grey);
+ }
+ break;
+ case SPAM_SYNC_TRAPPED:
+- sd = (struct spam_synctlv_addr *)tlv;
+- if (sizeof(*sd) != ntohs(tlv->st_length))
+- goto trunc;
++ if (hdr->sh_af == AF_INET) {
++ sd = (struct spam_synctlv_addr *)tlv;
++ if (sizeof(*sd) != ntohs(tlv->st_length))
++ goto trunc;
++
++ ip.s_addr = sd->sd_ip;
++ inet_ntop(AF_INET, &ip, ipstr, sizeof(ipstr));
++
++ expire = ntohl(sd->sd_expire);
++ } else {
++ sd6 = (struct spam_synctlv_addr6 *)tlv;
++ if (sizeof(*sd6) != ntohs(tlv->st_length))
++ goto trunc;
++
++ memcpy(&ip6.s6_addr, &sd6->sd6_ip, sizeof(ip6.s6_addr));
++ inet_ntop(AF_INET6, &ip6, ipstr, sizeof(ipstr));
++
++ expire = ntohl(sd6->sd6_expire);
++ }
+
+- ip.s_addr = sd->sd_ip;
+- expire = ntohl(sd->sd_expire);
+ if (debug) {
+ fprintf(stderr, "%s(sync): "
+ "received trapped entry ",
+ inet_ntoa(addr.sin_addr));
+- fprintf(stderr, "ip %s ", inet_ntoa(ip));
++ fprintf(stderr, "ip %s ", ipstr);
+ }
+ if (greylist) {
+ /* send this info to the greylister */
+- fprintf(grey, "TRAP:%s:", inet_ntoa(ip));
+- fprintf(grey, "%s:%u\n",
++ fprintf(grey, "TRAP;%s;", ipstr);
++ fprintf(grey, "%s;%u\n",
+ inet_ntoa(addr.sin_addr), expire);
+ fflush(grey);
+ }
+@@ -420,7 +472,10 @@
+ struct iovec iov[7];
+ struct spam_synchdr hdr;
+ struct spam_synctlv_grey sg;
++ struct spam_synctlv_grey6 sg6;
++
+ struct spam_synctlv_hdr end;
++ struct addrinfo hints, *res;
+ u_int16_t sglen, fromlen, tolen, helolen, padlen;
+ char pad[SPAM_ALIGNBYTES];
+ int i = 0;
+@@ -434,8 +489,20 @@
+
+ bzero(&hdr, sizeof(hdr));
+ bzero(&sg, sizeof(sg));
++ bzero(&sg6, sizeof(sg6));
+ bzero(&pad, sizeof(pad));
+
++ memset(&hints, 0, sizeof(hints));
++ hints.ai_family = PF_UNSPEC;
++ hints.ai_socktype = SOCK_DGRAM; /* dummy */
++ hints.ai_protocol = IPPROTO_UDP; /* dummy */
++ hints.ai_flags = AI_NUMERICHOST;
++
++ if (getaddrinfo(ip, NULL, &hints, &res) != 0) {
++ warnx("Invalid ip address: %s", ip);
++ return;
++ }
++
+ fromlen = strlen(from) + 1;
+ tolen = strlen(to) + 1;
+ helolen = strlen(helo) + 1;
+@@ -443,12 +510,15 @@
+ HMAC_CTX_init(&ctx);
+ HMAC_Init(&ctx, sync_key, strlen(sync_key), EVP_sha1());
+
+- sglen = sizeof(sg) + fromlen + tolen + helolen;
++ if (res->ai_family == AF_INET)
++ sglen = sizeof(sg) + fromlen + tolen + helolen;
++ else
++ sglen = sizeof(sg6) + fromlen + tolen + helolen;
+ padlen = SPAM_ALIGN(sglen) - sglen;
+
+ /* Add SPAM sync packet header */
+ hdr.sh_version = SPAM_SYNC_VERSION;
+- hdr.sh_af = AF_INET;
++ hdr.sh_af = res->ai_family;
+ hdr.sh_counter = htonl(sync_counter++);
+ hdr.sh_length = htons(sizeof(hdr) + sglen + padlen + sizeof(end));
+ iov[i].iov_base = &hdr;
+@@ -456,16 +526,35 @@
+ HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
+ i++;
+
+- /* Add single SPAM sync greylisting entry */
+- sg.sg_type = htons(SPAM_SYNC_GREY);
+- sg.sg_length = htons(sglen + padlen);
+- sg.sg_timestamp = htonl(now);
+- sg.sg_ip = inet_addr(ip);
+- sg.sg_from_length = htons(fromlen);
+- sg.sg_to_length = htons(tolen);
+- sg.sg_helo_length = htons(helolen);
+- iov[i].iov_base = &sg;
+- iov[i].iov_len = sizeof(sg);
++ if (res->ai_family == AF_INET) {
++ /* Add single SPAM sync greylisting entry */
++ sg.sg_type = htons(SPAM_SYNC_GREY);
++ sg.sg_length = htons(sglen + padlen);
++ sg.sg_timestamp = htonl(now);
++
++ inet_pton(AF_INET, ip, &sg.sg_ip);
++
++ sg.sg_from_length = htons(fromlen);
++ sg.sg_to_length = htons(tolen);
++ sg.sg_helo_length = htons(helolen);
++ iov[i].iov_base = &sg;
++ iov[i].iov_len = sizeof(sg);
++ } else {
++ /* Add single SPAM sync greylisting entry */
++ sg6.sg6_type = htons(SPAM_SYNC_GREY);
++ sg6.sg6_length = htons(sglen + padlen);
++ sg6.sg6_timestamp = htonl(now);
++
++ inet_pton(AF_INET6, ip, &sg6.sg6_ip);
++
++ sg6.sg6_from_length = htons(fromlen);
++ sg6.sg6_to_length = htons(tolen);
++ sg6.sg6_helo_length = htons(helolen);
++ iov[i].iov_base = &sg6;
++ iov[i].iov_len = sizeof(sg6);
++ }
++ freeaddrinfo(res);
++
+ HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
+ i++;
+
+@@ -510,7 +599,9 @@
+ struct iovec iov[3];
+ struct spam_synchdr hdr;
+ struct spam_synctlv_addr sd;
++ struct spam_synctlv_addr6 sd6;
+ struct spam_synctlv_hdr end;
++ struct addrinfo hints, *res;
+ int i = 0;
+ HMAC_CTX ctx;
+ u_int hmac_len;
+@@ -521,28 +612,60 @@
+
+ bzero(&hdr, sizeof(hdr));
+ bzero(&sd, sizeof(sd));
++ bzero(&sd6, sizeof(sd6));
++
++ memset(&hints, 0, sizeof(hints));
++ hints.ai_family = PF_UNSPEC;
++ hints.ai_socktype = SOCK_DGRAM; /* dummy */
++ hints.ai_protocol = IPPROTO_UDP; /* dummy */
++ hints.ai_flags = AI_NUMERICHOST;
++
++ if (getaddrinfo(ip, NULL, &hints, &res) != 0) {
++ warnx("Invalid ip address: %s", ip);
++ return;
++ }
+
+ HMAC_CTX_init(&ctx);
+ HMAC_Init(&ctx, sync_key, strlen(sync_key), EVP_sha1());
+
+ /* Add SPAM sync packet header */
+ hdr.sh_version = SPAM_SYNC_VERSION;
+- hdr.sh_af = AF_INET;
++ hdr.sh_af = res->ai_family;
+ hdr.sh_counter = htonl(sync_counter++);
+- hdr.sh_length = htons(sizeof(hdr) + sizeof(sd) + sizeof(end));
++ if (res->ai_family == AF_INET)
++ hdr.sh_length = htons(sizeof(hdr) + sizeof(sd) + sizeof(end));
++ else
++ hdr.sh_length = htons(sizeof(hdr) + sizeof(sd6) + sizeof(end));
+ iov[i].iov_base = &hdr;
+ iov[i].iov_len = sizeof(hdr);
+ HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
+ i++;
+
+- /* Add single SPAM sync address entry */
+- sd.sd_type = htons(type);
+- sd.sd_length = htons(sizeof(sd));
+- sd.sd_timestamp = htonl(now);
+- sd.sd_expire = htonl(expire);
+- sd.sd_ip = inet_addr(ip);
+- iov[i].iov_base = &sd;
+- iov[i].iov_len = sizeof(sd);
++ if (res->ai_family == AF_INET) {
++ /* Add single SPAM sync address entry */
++ sd.sd_type = htons(type);
++ sd.sd_length = htons(sizeof(sd));
++ sd.sd_timestamp = htonl(now);
++ sd.sd_expire = htonl(expire);
++
++ inet_pton(AF_INET, ip, &sd.sd_ip);
++
++ iov[i].iov_base = &sd;
++ iov[i].iov_len = sizeof(sd);
++ } else {
++ /* Add single SPAM sync address entry */
++ sd6.sd6_type = htons(type);
++ sd6.sd6_length = htons(sizeof(sd));
++ sd6.sd6_timestamp = htonl(now);
++ sd6.sd6_expire = htonl(expire);
++
++ inet_pton(AF_INET6, ip, &sd6.sd6_ip);
++
++ iov[i].iov_base = &sd6;
++ iov[i].iov_len = sizeof(sd6);
++ }
++ freeaddrinfo(res);
++
+ HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
+ i++;
+
+Index: sync.h
+===================================================================
+RCS file: /cvs/src/libexec/spamd/sync.h,v
+retrieving revision 1.3
+diff -u -r1.3 sync.h
+--- sync.h 22 May 2008 19:54:11 -0000 1.3
++++ sync.h 31 Dec 2015 20:25:24 -0000
+@@ -68,12 +68,31 @@
+ /* strings go here, then packet code re-aligns packet */
+ } __packed;
+
++struct spam_synctlv_grey6 {
++ u_int16_t sg6_type;
++ u_int16_t sg6_length;
++ u_int32_t sg6_timestamp;
++ u_int32_t sg6_ip[4];
++ u_int16_t sg6_from_length;
++ u_int16_t sg6_to_length;
++ u_int16_t sg6_helo_length;
++ /* strings go here, then packet code re-aligns packet */
++} __packed;
++
+ struct spam_synctlv_addr {
+ u_int16_t sd_type;
+ u_int16_t sd_length;
+ u_int32_t sd_timestamp;
+ u_int32_t sd_expire;
+ u_int32_t sd_ip;
++} __packed;
++
++struct spam_synctlv_addr6 {
++ u_int16_t sd6_type;
++ u_int16_t sd6_length;
++ u_int32_t sd6_timestamp;
++ u_int32_t sd6_expire;
++ u_int32_t sd6_ip[4];
+ } __packed;
+
+ #define SPAM_SYNC_END 0x0000
diff --git a/spamlogd.patch b/spamlogd.patch
new file mode 100644
index 0000000..0d6527f
--- /dev/null
+++ b/spamlogd.patch
@@ -0,0 +1,78 @@
+Index: spamlogd.c
+===================================================================
+RCS file: /cvs/src/libexec/spamlogd/spamlogd.c,v
+retrieving revision 1.26
+diff -u -r1.26 spamlogd.c
+--- spamlogd.c 11 Dec 2015 17:16:52 -0000 1.26
++++ spamlogd.c 31 Dec 2015 15:48:03 -0000
+@@ -32,6 +32,7 @@
+
+ #include <netinet/in.h>
+ #include <netinet/ip.h>
++#include <netinet/ip6.h>
+ #include <arpa/inet.h>
+
+ #include <net/pfvar.h>
+@@ -110,8 +111,9 @@
+ init_pcap(void)
+ {
+ struct bpf_program bpfp;
+- char filter[PCAPFSIZ] = "ip and port 25 and action pass "
+- "and tcp[13]&0x12=0x2";
++ char filter[PCAPFSIZ] = "tcp and port 25 and action pass "
++ "and ((ip and tcp[13] & 0x12 = 0x2) or "
++ "(ip6 and ip6[6] = 0x6 and ip6[53] & 0x12 = 0x2))";
+
+ if ((hpcap = pcap_open_live(pflogif, PCAPSNAP, 1, PCAPTIMO,
+ errbuf)) == NULL) {
+@@ -155,6 +157,7 @@
+ u_int8_t hdrlen;
+ u_int32_t caplen = h->caplen;
+ const struct ip *ip = NULL;
++ const struct ip6_hdr *ip6 = NULL;
+ const struct pfloghdr *hdr;
+ char ipstraddr[40] = { '\0' };
+
+@@ -185,6 +188,14 @@
+ else if (hdr->dir == PF_OUT && !flag_inbound)
+ inet_ntop(af, &ip->ip_dst, ipstraddr,
+ sizeof(ipstraddr));
++ } else if (af == AF_INET6) {
++ ip6 = (const struct ip6_hdr *)(sp + hdrlen);
++ if (hdr->dir == PF_IN)
++ inet_ntop(af, &ip6->ip6_src, ipstraddr,
++ sizeof(ipstraddr));
++ else if (hdr->dir == PF_OUT && !flag_inbound)
++ inet_ntop(af, &ip6->ip6_dst, ipstraddr,
++ sizeof(ipstraddr));
+ }
+
+ if (ipstraddr[0] != '\0') {
+@@ -205,7 +216,12 @@
+ struct gdata gd;
+ time_t now;
+ int r;
+- struct in_addr ia;
++ struct addrinfo hints, *res;
++ memset(&hints, 0, sizeof(hints));
++ hints.ai_family = PF_UNSPEC;
++ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
++ hints.ai_protocol = IPPROTO_UDP; /*dummy*/
++ hints.ai_flags = AI_NUMERICHOST;
+
+ now = time(NULL);
+ memset(&hashinfo, 0, sizeof(hashinfo));
+@@ -215,10 +231,12 @@
+ strerror(errno));
+ return (-1);
+ }
+- if (inet_pton(AF_INET, ip, &ia) != 1) {
++ if (getaddrinfo(ip, NULL, &hints, &res) != 0) {
+ logmsg(LOG_NOTICE, "Invalid IP address %s", ip);
+ goto bad;
+ }
++ freeaddrinfo(res);
++
+ memset(&dbk, 0, sizeof(dbk));
+ dbk.size = strlen(ip);
+ dbk.data = ip;