root/libexec/spamd-setup/spamd-setup.c
/*      $OpenBSD: spamd-setup.c,v 1.50 2017/07/07 00:10:15 djm Exp $ */

/*
 * Copyright (c) 2003 Bob Beck.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zlib.h>

#define PATH_FTP                "/usr/bin/ftp"
#define PATH_PFCTL              "/sbin/pfctl"
#define PATH_SPAMD_CONF         "/etc/mail/spamd.conf"
#define SPAMD_ARG_MAX           256 /* max # of args to an exec */
#define SPAMD_USER              "_spamd"

struct cidr {
        u_int32_t addr;
        u_int8_t bits;
};

struct bl {
        u_int32_t addr;
        int8_t b;
        int8_t w;
};

struct blacklist {
        char *name;
        char *message;
        struct bl *bl;
        size_t blc, bls;
        u_int8_t black;
};

u_int32_t        imask(u_int8_t);
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);
void             cidr2range(struct cidr, u_int32_t *, u_int32_t *);
char            *atop(u_int32_t);
int              parse_netblock(char *, struct bl *, struct bl *, int);
int              open_child(char *, char **, int);
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              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              getlist(char **, char *, struct blacklist *, struct blacklist *);
__dead void      usage(void);

uid_t             spamd_uid;
gid_t             spamd_gid;
int               debug;
int               dryrun;
int               greyonly = 1;

extern char      *__progname;

#define MAXIMUM(a,b) (((a)>(b))?(a):(b))

u_int32_t
imask(u_int8_t b)
{
        if (b == 0)
                return (0);
        return (0xffffffffU << (32 - b));
}

u_int8_t
maxblock(u_int32_t addr, u_int8_t bits)
{
        u_int32_t m;

        while (bits > 0) {
                m = imask(bits - 1);

                if ((addr & m) != addr)
                        return (bits);
                bits--;
        }
        return (bits);
}

u_int8_t
maxdiff(u_int32_t a, u_int32_t b)
{
        u_int8_t bits = 0;
        u_int32_t m;

        b++;
        while (bits < 32) {
                m = imask(bits);

                if ((a & m) != (b & m))
                        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)
{
        u_int8_t maxsize, diff;
        struct cidr *tmp;

        while (end >= start) {
                maxsize = maxblock(start, 32);
                diff = maxdiff(start, end);

                maxsize = MAXIMUM(maxsize, diff);
                if (*cls <= *cli + 1) {         /* one extra for terminator */
                        tmp = reallocarray(list, *cls + 32,
                            sizeof(struct cidr));
                        if (tmp == NULL)
                                err(1, NULL);
                        list = tmp;
                        *cls += 32;
                }
                list[*cli].addr = start;
                list[*cli].bits = maxsize;
                (*cli)++;
                start = start + (1 << (32 - maxsize));
        }
        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;
}

char *
atop(u_int32_t addr)
{
        struct in_addr in;

        memset(&in, 0, sizeof(in));
        in.s_addr = htonl(addr);
        return (inet_ntoa(in));
}

int
parse_netblock(char *buf, struct bl *start, struct bl *end, int white)
{
        char astring[16], astring2[16];
        unsigned maskbits;
        struct cidr c;

        /* skip leading spaces */
        while (*buf == ' ')
                buf++;
        /* bail if it's a comment */
        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)
                        return (0);
                c.addr = ntohl(c.addr);
                if (maskbits > 32)
                        return (0);
                c.bits = maskbits;
                cidr2range(c, &start->addr, &end->addr);
                end->addr += 1;
        } 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)
                        return (0);
                end->addr = ntohl(end->addr) + 1;
                if (start > end)
                        return (0);
        } 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)
                        return (0);
                start->addr = ntohl(start->addr);
                end->addr = start->addr + 1;
        } else
                return (0);

        if (white) {
                start->b = 0;
                start->w = 1;
                end->b = 0;
                end->w = -1;
        } else {
                start->b = 1;
                start->w = 0;
                end->b = -1;
                end->w = 0;
        }
        return (1);
}

void
drop_privileges(void)
{
        if (setgroups(1, &spamd_gid) != 0)
                err(1, "setgroups %ld", (long)spamd_gid);
        if (setresgid(spamd_gid, spamd_gid, spamd_gid) != 0)
                err(1, "setresgid %ld", (long)spamd_gid);
        if (setresuid(spamd_uid, spamd_uid, spamd_uid) != 0)
                err(1, "setresuid %ld", (long)spamd_uid);
}

int
open_child(char *file, char **argv, int drop_privs)
{
        int pdes[2];

        if (pipe(pdes) != 0)
                return (-1);
        switch (fork()) {
        case -1:
                close(pdes[0]);
                close(pdes[1]);
                return (-1);
        case 0:
                /* child */
                close(pdes[0]);
                if (pdes[1] != STDOUT_FILENO) {
                        dup2(pdes[1], STDOUT_FILENO);
                        close(pdes[1]);
                }
                if (drop_privs)
                        drop_privileges();
                closefrom(STDERR_FILENO + 1);
                execvp(file, argv);
                _exit(1);
        }

        /* parent */
        close(pdes[1]);
        return (pdes[0]);
}

int
fileget(char *url)
{
        char *argv[6];

        argv[0] = "ftp";
        argv[1] = "-V";
        argv[2] = "-o";
        argv[3] = "-";
        argv[4] = url;
        argv[5] = NULL;

        if (debug)
                fprintf(stderr, "Getting %s\n", url);

        return (open_child(PATH_FTP, argv, 1));
}

int
open_file(char *method, char *file)
{
        char *url;
        char **ap, **argv;
        int len, i, oerrno;

        if ((method == NULL) || (strcmp(method, "file") == 0))
                return (open(file, O_RDONLY));
        if (strcmp(method, "http") == 0 || strcmp(method, "https") == 0 ||
            strcmp(method, "ftp") == 0) {
                if (asprintf(&url, "%s://%s", method, file) == -1)
                        return (-1);
                i = fileget(url);
                free(url);
                return (i);
        } else if (strcmp(method, "exec") == 0) {
                len = strlen(file);
                argv = calloc(len, sizeof(char *));
                if (argv == NULL)
                        return (-1);
                for (ap = argv; ap < &argv[len - 1] &&
                    (*ap = strsep(&file, " \t")) != NULL;) {
                        if (**ap != '\0')
                                ap++;
                }
                *ap = NULL;
                i = open_child(argv[0], argv, 0);
                oerrno = errno;
                free(argv);
                errno = oerrno;
                return (i);
        }
        errx(1, "Unknown method %s", method);
        return (-1); /* NOTREACHED */
}

/*
 * fix_quoted_colons walks through a buffer returned by cgetent.  We
 * look for quoted strings, to escape colons (:) in quoted strings for
 * getcap by replacing them with \C so cgetstr() deals with it correctly
 * without having to see the \C bletchery in a configuration file that
 * needs to have urls in it. Frees the buffer passed to it, passes back
 * another larger one, with can be used with cgetxxx(), like the original
 * buffer, it must be freed by the caller.
 * This should really be a temporary fix until there is a sanctioned
 * way to make getcap(3) handle quoted strings like this in a nicer
 * way.
 */
char *
fix_quoted_colons(char *buf)
{
        int in = 0;
        size_t i, j = 0;
        char *newbuf, last;

        /* Allocate enough space for a buf of all colons (impossible). */
        newbuf = malloc(2 * strlen(buf) + 1);
        if (newbuf == NULL)
                return (NULL);
        last = '\0';
        for (i = 0; i < strlen(buf); i++) {
                switch (buf[i]) {
                case ':':
                        if (in) {
                                newbuf[j++] = '\\';
                                newbuf[j++] = 'C';
                        } else
                                newbuf[j++] = buf[i];
                        break;
                case '"':
                        if (last != '\\')
                                in = !in;
                        newbuf[j++] = buf[i];
                        break;
                default:
                        newbuf[j++] = buf[i];
                }
                last = buf[i];
        }
        free(buf);
        newbuf[j] = '\0';
        return (newbuf);
}

void
do_message(FILE *sdc, char *msg)
{
        size_t i, bs = 0, bu = 0, len;
        ssize_t n;      
        char *buf = NULL, last, *tmp;
        int fd;

        len = strlen(msg);
        if (msg[0] == '"' && msg[len - 1] == '"') {
                /* quoted msg, escape newlines and send it out */
                msg[len - 1] = '\0';
                buf = msg + 1;
                bu = len - 2;
                goto sendit;
        } else {
                /*
                 * message isn't quoted - try to open a local
                 * file and read the message from it.
                 */
                fd = open(msg, O_RDONLY);
                if (fd == -1)
                        err(1, "Can't open message from %s", msg);
                for (;;) {
                        if (bu == bs) {
                                tmp = realloc(buf, bs + 8192);
                                if (tmp == NULL)
                                        err(1, NULL);
                                bs += 8192;
                                buf = tmp;
                        }

                        n = read(fd, buf + bu, bs - bu);
                        if (n == 0) {
                                goto sendit;
                        } else if (n == -1) {
                                err(1, "Can't read from %s", msg);
                        } else
                                bu += n;
                }
                buf[bu]='\0';
        }
 sendit:
        fprintf(sdc, ";\"");
        last = '\0';
        for (i = 0; i < bu; i++) {
                /* handle escaping the things spamd wants */
                switch (buf[i]) {
                case 'n':
                        if (last == '\\')
                                fprintf(sdc, "\\\\n");
                        else
                                fputc('n', sdc);
                        last = '\0';
                        break;
                case '\n':
                        fprintf(sdc, "\\n");
                        last = '\0';
                        break;
                case '"':
                        fputc('\\', sdc);
                        /* FALLTHROUGH */
                default:
                        fputc(buf[i], sdc);
                        last = '\0';
                }
        }
        fputc('"', sdc);
        if (bs != 0)
                free(buf);
}

/* 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 i, n, start, bu = 0, bs = 0, serrno = 0;
        char *buf = NULL, *tmp;
        struct bl *blt;

        for (;;) {
                /* read in gzf, then parse */
                if (bu == bs) {
                        tmp = realloc(buf, bs + (1024 * 1024) + 1);
                        if (tmp == NULL) {
                                serrno = errno;
                                free(buf);
                                buf = NULL;
                                bs = 0;
                                goto bldone;
                        }
                        bs += 1024 * 1024;
                        buf = tmp;
                }

                n = gzread(gzf, buf + bu, bs - bu);
                if (n == 0)
                        goto parse;
                else if (n == -1) {
                        serrno = errno;
                        goto bldone;
                } else
                        bu += n;
        }
 parse:
        start = 0;
        /* 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));
                if (blt == NULL) {
                        *bls -= bu / 7;
                        serrno = errno;
                        goto bldone;
                }
                bl = blt;
        }
        for (i = 0; i <= bu; i++) {
                if (*blc + 1 >= *bls) {
                        *bls += 1024;
                        blt = reallocarray(bl, *bls, sizeof(struct bl));
                        if (blt == NULL) {
                                *bls -= 1024;
                                serrno = errno;
                                goto bldone;
                        }
                        bl = blt;
                }
                if (i == bu || buf[i] == '\n') {
                        buf[i] = '\0';
                        if (parse_netblock(buf + start,
                            bl + *blc, bl + *blc + 1, white))
                                *blc += 2;
                        start = i + 1;
                }
        }
        if (bu == 0)
                errno = EIO;
 bldone:
        free(buf);
        if (serrno)
                errno = serrno;
        return (bl);
}

int
cmpbl(const void *a, const void *b)
{
        if (((struct bl *)a)->addr > ((struct bl *) b)->addr)
                return (1);
        if (((struct bl *)a)->addr < ((struct bl *) b)->addr)
                return (-1);
        return (0);
}

/*
 * 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.
 */
struct cidr *
collapse_blacklist(struct bl *bl, size_t blc, u_int *clc)
{
        int bs = 0, ws = 0, state=0;
        u_int cli, cls, i;
        u_int32_t bstart = 0;
        struct cidr *cl;
        int laststate;
        u_int32_t addr;

        if (blc == 0)
                return (NULL);

        /*
         * Overallocate by 10% to avoid excessive realloc due to white
         * entries splitting up CIDR blocks.
         */
        cli = 0;
        cls = (blc / 2) + (blc / 20) + 1;
        cl = reallocarray(NULL, cls, sizeof(struct cidr));
        if (cl == NULL)
                return (NULL);
        qsort(bl, blc, sizeof(struct bl), cmpbl);
        for (i = 0; i < blc;) {
                laststate = state;
                addr = bl[i].addr;

                do {
                        bs += bl[i].b;
                        ws += bl[i].w;
                        i++;
                } while (bl[i].addr == addr);
                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 */
                        bstart = addr;
                }
                if (laststate == 1 && state == 0) {
                        /* end blacklist */
                        cl = range2cidrlist(cl, &cli, &cls, bstart, addr - 1);
                }
                laststate = state;
        }
        cl[cli].addr = 0;
        *clc = cli;
        return (cl);
}

int
configure_spamd(u_short dport, char *name, char *message,
    struct cidr *blacklists, u_int count)
{
        int lport = IPPORT_RESERVED - 1, s;
        struct sockaddr_in sin;
        FILE* sdc;

        s = rresvport(&lport);
        if (s == -1)
                return (-1);
        memset(&sin, 0, sizeof sin);
        sin.sin_len = sizeof(sin);
        sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
        sin.sin_family = AF_INET;
        sin.sin_port = htons(dport);
        if (connect(s, (struct sockaddr *)&sin, sizeof sin) == -1)
                return (-1);
        sdc = fdopen(s, "w");
        if (sdc == NULL) {
                close(s);
                return (-1);
        }
        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++;
        }
        fputc('\n', sdc);
        fclose(sdc);
        close(s);
        return (0);
}


int
configure_pf(struct cidr *blacklists)
{
        char *argv[9]= {"pfctl", "-q", "-t", "spamd", "-T", "replace",
            "-f" "-", NULL};
        static FILE *pf = NULL;
        int pdes[2];

        if (pf == NULL) {
                if (pipe(pdes) != 0)
                        return (-1);
                switch (fork()) {
                case -1:
                        close(pdes[0]);
                        close(pdes[1]);
                        return (-1);
                case 0:
                        /* child */
                        close(pdes[1]);
                        if (pdes[0] != STDIN_FILENO) {
                                dup2(pdes[0], STDIN_FILENO);
                                close(pdes[0]);
                        }
                        closefrom(STDERR_FILENO + 1);
                        execvp(PATH_PFCTL, argv);
                        _exit(1);
                }

                /* parent */
                close(pdes[0]);
                pf = fdopen(pdes[1], "w");
                if (pf == NULL) {
                        close(pdes[1]);
                        return (-1);
                }
        }
        while (blacklists->addr != 0) {
                fprintf(pf, "%s/%u\n", atop(blacklists->addr),
                    blacklists->bits);
                blacklists++;
        }
        return (0);
}

int
getlist(char ** db_array, char *name, struct blacklist *blist,
    struct blacklist *blistnew)
{
        char *buf, *method, *file, *message;
        int fd, black = 0, serror;
        size_t blc, bls;
        struct bl *bl = NULL;
        gzFile gzf;

        if (cgetent(&buf, db_array, name) != 0)
                err(1, "Can't find \"%s\" in spamd config", name);
        buf = fix_quoted_colons(buf);
        if (cgetcap(buf, "black", ':') != NULL) {
                /* use new list */
                black = 1;
                blc = blistnew->blc;
                bls = blistnew->bls;
                bl = blistnew->bl;
        } else if (cgetcap(buf, "white", ':') != NULL) {
                /* apply to most recent blacklist */
                black = 0;
                blc = blist->blc;
                bls = blist->bls;
                bl = blist->bl;
        } else
                errx(1, "Must have \"black\" or \"white\" in %s", name);

        switch (cgetstr(buf, "msg", &message)) {
        case -1:
                if (black)
                        errx(1, "No msg for blacklist \"%s\"", name);
                break;
        case -2:
                err(1, NULL);
        }

        switch (cgetstr(buf, "method", &method)) {
        case -1:
                method = NULL;
                break;
        case -2:
                err(1, NULL);
        }

        switch (cgetstr(buf, "file", &file)) {
        case -1:
                errx(1, "No file given for %slist %s",
                    black ? "black" : "white", name);
        case -2:
                err(1, NULL);
        default:
                fd = open_file(method, file);
                if (fd == -1)
                        err(1, "Can't open %s by %s method",
                            file, method ? method : "file");
                free(method);
                free(file);
                gzf = gzdopen(fd, "r");
                if (gzf == NULL)
                        errx(1, "gzdopen");
        }
        free(buf);
        bl = add_blacklist(bl, &blc, &bls, gzf, !black);
        serror = errno;
        gzclose(gzf);
        if (bl == NULL) {
                errno = serror;
                warn("Could not add %slist %s", black ? "black" : "white",
                    name);
                return (0);
        }
        if (black) {
                if (debug)
                        fprintf(stderr, "blacklist %s %zu entries\n",
                            name, blc / 2);
                blistnew->message = message;
                blistnew->name = name;
                blistnew->black = black;
                blistnew->bl = bl;
                blistnew->blc = blc;
                blistnew->bls = bls;
        } else {
                /* whitelist applied to last active blacklist */
                if (debug)
                        fprintf(stderr, "whitelist %s %zu entries\n",
                            name, (blc - blist->blc) / 2);
                blist->bl = bl;
                blist->blc = blc;
                blist->bls = bls;
        }
        return (black);
}

void
send_blacklist(struct blacklist *blist, in_port_t port)
{
        struct cidr *cidrs;
        u_int clc;

        if (blist->blc > 0) {
                cidrs = collapse_blacklist(blist->bl, blist->blc, &clc);
                if (cidrs == NULL)
                        err(1, NULL);
                if (!dryrun) {
                        if (configure_spamd(port, blist->name,
                            blist->message, cidrs, clc) == -1)
                                err(1, "Can't connect to spamd on port %d",
                                    port);
                        if (!greyonly && configure_pf(cidrs) == -1)
                                err(1, "pfctl failed");
                }
                free(cidrs);
                free(blist->bl);
        }
}

__dead void
usage(void)
{

        fprintf(stderr, "usage: %s [-bDdn]\n", __progname);
        exit(1);
}

int
main(int argc, char *argv[])
{
        size_t blc, bls, black, white;
        char *db_array[2], *buf, *name;
        struct blacklist *blists;
        struct servent *ent;
        int daemonize = 0, ch;
        struct passwd *pw;

        while ((ch = getopt(argc, argv, "bdDn")) != -1) {
                switch (ch) {
                case 'n':
                        dryrun = 1;
                        break;
                case 'd':
                        debug = 1;
                        break;
                case 'b':
                        greyonly = 0;
                        break;
                case 'D':
                        daemonize = 1;
                        break;
                default:
                        usage();
                        break;
                }
        }
        argc -= optind;
        argv += optind;
        if (argc != 0)
                usage();

        if ((pw = getpwnam(SPAMD_USER)) == NULL)
                errx(1, "cannot find user %s", SPAMD_USER);
        spamd_uid = pw->pw_uid;
        spamd_gid = pw->pw_gid;

        if (pledge("stdio rpath inet proc exec id", NULL) == -1)
                err(1, "pledge");

        if (daemonize)
                daemon(0, 0);
        else if (chdir("/") != 0)
                err(1, "chdir(\"/\")");

        if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL)
                errx(1, "cannot find service \"spamd-cfg\" in /etc/services");
        ent->s_port = ntohs(ent->s_port);

        db_array[0] = PATH_SPAMD_CONF;
        db_array[1] = NULL;

        if (cgetent(&buf, db_array, "all") != 0)
                err(1, "Can't find \"all\" in spamd config");
        name = strsep(&buf, ": \t"); /* skip "all" at start */
        blists = NULL;
        blc = bls = 0;
        while ((name = strsep(&buf, ": \t")) != NULL) {
                if (*name) {
                        /* extract config in order specified in "all" tag */
                        if (blc == bls) {
                                struct blacklist *tmp;

                                bls += 32;
                                tmp = reallocarray(blists, bls,
                                    sizeof(struct blacklist));
                                if (tmp == NULL)
                                        err(1, NULL);
                                blists = tmp;
                        }
                        if (blc == 0)
                                black = white = 0;
                        else {
                                white = blc - 1;
                                black = blc;
                        }
                        memset(&blists[black], 0, sizeof(struct blacklist));
                        black = getlist(db_array, name, &blists[white],
                            &blists[black]);
                        if (black && blc > 0) {
                                /* collapse and free previous blacklist */
                                send_blacklist(&blists[blc - 1], ent->s_port);
                        }
                        blc += black;
                }
        }
        /* collapse and free last blacklist */
        if (blc > 0)
                send_blacklist(&blists[blc - 1], ent->s_port);
        return (0);
}