/* $OpenBSD: sdl.c,v 1.25 2022/12/26 20:06:43 jmc Exp $ */ /* * Copyright (c) 2003-2007 Bob Beck. All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * sdl.c - Implement spamd source lists * * This consists of everything we need to do to determine which lists * someone is on. Spamd gets the connecting address, and looks it up * against all lists to determine what deferral messages to feed back * to the connecting machine. - The redirection to spamd will happen * from pf in the kernel, first match will divert to us. Spamd (along with * setup) must keep track of *all* matches, so as to tell someone all the * lists that they are on. */ #include #include #include #include #include #include #include #include #include "sdl.h" static void sdl_free(struct sdlist *); static void sdl_clear(struct sdlist *); extern int debug; struct sdlist *blacklists = NULL; int blc = 0, blu = 0; static int compar_v4(const void *va, const void *vb) { const struct sdentry_v4 *a = va; const struct sdentry_v4 *b = vb; struct in_addr aa; struct in_addr bb; /* The mask has already been applied. */ aa.s_addr = ntohl(a->sda.s_addr); bb.s_addr = ntohl(b->sda.s_addr); if (aa.s_addr > bb.s_addr) return (1); if (aa.s_addr < bb.s_addr) return (-1); return (0); } static int compar_v6(const void *va, const void *vb) { const struct sdentry_v6 *a = va; const struct sdentry_v6 *b = vb; struct sdaddr_v6 aa; struct sdaddr_v6 bb; /* The mask has already been applied. */ aa.addr32[0] = ntohl(a->sda.addr32[0]); aa.addr32[1] = ntohl(a->sda.addr32[1]); aa.addr32[2] = ntohl(a->sda.addr32[2]); aa.addr32[3] = ntohl(a->sda.addr32[3]); bb.addr32[0] = ntohl(b->sda.addr32[0]); bb.addr32[1] = ntohl(b->sda.addr32[1]); bb.addr32[2] = ntohl(b->sda.addr32[2]); bb.addr32[3] = ntohl(b->sda.addr32[3]); if (aa.addr32[0] > bb.addr32[0]) return (1); if (aa.addr32[0] < bb.addr32[0]) return (-1); if (aa.addr32[1] > bb.addr32[1]) return (1); if (aa.addr32[1] < bb.addr32[1]) return (-1); if (aa.addr32[2] > bb.addr32[2]) return (1); if (aa.addr32[2] < bb.addr32[2]) return (-1); if (aa.addr32[3] > bb.addr32[3]) return (1); if (aa.addr32[3] < bb.addr32[3]) return (-1); return (0); } int sdl_add(char *sdname, char *sdstring, char **v4, u_int nv4, char **v6, u_int nv6) { int i, idx = -1; char astring[40]; char *addr = NULL; unsigned int maskbits; /* * if a blacklist of same tag name is already there, replace it, * otherwise append. */ for (i = 0; i < blu; i++) { if (strcmp(blacklists[i].tag, sdname) == 0) { idx = i; break; } } if (idx != -1) { if (debug > 0) printf("replacing list %s; %u new entries\n", blacklists[idx].tag, nv4 + nv6); sdl_free(&blacklists[idx]); } else { if (debug > 0) printf("adding list %s; %u entries\n", sdname, nv4 + nv6); if (blu == blc) { struct sdlist *tmp; tmp = reallocarray(blacklists, blc + 128, sizeof(struct sdlist)); if (tmp == NULL) return (-1); blacklists = tmp; blc += 128; sdl_clear(&blacklists[blu]); } idx = blu; } if ((blacklists[idx].tag = strdup(sdname)) == NULL) goto misc_error; if ((blacklists[idx].string = strdup(sdstring)) == NULL) goto misc_error; /* * Cycle through addrs by family, converting. We assume they are * correctly formatted v4 and v6 addrs, if they don't all convert * correctly, the add fails. Each address should be address/maskbits. */ if (nv4 != 0) { blacklists[idx].v4.naddrs = nv4; blacklists[idx].v4.addrs = reallocarray(NULL, nv4, sizeof(struct sdentry_v4)); if (blacklists[idx].v4.addrs == NULL) goto misc_error; for (i = 0; i < nv4; i++) { struct in_addr *m, *n; int j; n = &blacklists[idx].v4.addrs[i].sda; m = &blacklists[idx].v4.addrs[i].sdm; addr = v4[i]; j = sscanf(addr, "%15[^/]/%u", astring, &maskbits); if (j != 2) goto parse_error; /* * sanity check! we don't allow a 0 mask - * don't blacklist the entire net. */ if (maskbits == 0 || maskbits > 32) goto parse_error; j = inet_pton(AF_INET, astring, n); if (j != 1) goto parse_error; if (debug > 0) printf("added %s/%u\n", astring, maskbits); /* set mask. */ m->s_addr = 0xffffffffU << (32 - maskbits); m->s_addr = htonl(m->s_addr); /* mask off address bits that won't ever be used */ n->s_addr = n->s_addr & m->s_addr; } /* spamd-setup output is sorted in host byte order */ mergesort(blacklists[idx].v4.addrs, nv4, sizeof(struct sdentry_v4), compar_v4); } if (nv6 != 0) { blacklists[idx].v6.naddrs = nv6; blacklists[idx].v6.addrs = reallocarray(NULL, nv6, sizeof(struct sdentry_v6)); if (blacklists[idx].v6.addrs == NULL) goto misc_error; for (i = 0; i < nv6; i++) { int j, k; struct sdaddr_v6 *m, *n; n = &blacklists[idx].v6.addrs[i].sda; m = &blacklists[idx].v6.addrs[i].sdm; addr = v6[i]; j = sscanf(addr, "%39[^/]/%u", astring, &maskbits); if (j != 2) goto parse_error; /* * sanity check! we don't allow a 0 mask - * don't blacklist the entire net. */ if (maskbits == 0 || maskbits > 128) goto parse_error; j = inet_pton(AF_INET6, astring, n); if (j != 1) goto parse_error; if (debug > 0) printf("added %s/%u\n", astring, maskbits); /* set mask, borrowed from pf */ k = 0; for (j = 0; j < 4; j++) m->addr32[j] = 0; while (maskbits >= 32) { m->addr32[k++] = 0xffffffffU; maskbits -= 32; } for (j = 31; j > 31 - maskbits; --j) m->addr32[k] |= (1 << j); if (maskbits) m->addr32[k] = htonl(m->addr32[k]); /* mask off address bits that won't ever be used */ for (j = 0; j < 4; j++) n->addr32[j] = n->addr32[j] & m->addr32[j]; } /* spamd-setup output is sorted in host byte order */ mergesort(blacklists[idx].v6.addrs, nv6, sizeof(struct sdentry_v6), compar_v6); } if (idx == blu) { blu++; sdl_clear(&blacklists[blu]); } return (0); parse_error: if (debug > 0) printf("sdl_add: parse error, \"%s\"\n", addr); misc_error: sdl_free(&blacklists[idx]); if (idx != blu) { memmove(&blacklists[idx], &blacklists[idx + 1], (blu - idx) * sizeof(*blacklists)); blu--; } return (-1); } void sdl_del(char *sdname) { int i, idx = -1; for (i = 0; i < blu; i++) { if (strcmp(blacklists[i].tag, sdname) == 0) { idx = i; break; } } if (idx != -1) { if (debug > 0) printf("clearing list %s\n", sdname); /* Must preserve tag. */ free(blacklists[idx].string); free(blacklists[idx].v4.addrs); free(blacklists[idx].v6.addrs); blacklists[idx].string = NULL; blacklists[idx].v4.addrs = NULL; blacklists[idx].v6.addrs = NULL; blacklists[idx].v4.naddrs = 0; blacklists[idx].v6.naddrs = 0; } } /* * Return 0 if the address a (with mask m) matches address key * otherwise return 1 if a > key or -1 if a < key. It is assumed * that address a has been pre-masked out, we only need to mask key. */ static int match_addr_v4(const void *vkey, const void *ventry) { const struct in_addr *k = vkey; const struct in_addr *a = &((const struct sdentry_v4 *)ventry)->sda; const struct in_addr *m = &((const struct sdentry_v4 *)ventry)->sdm; struct in_addr kk; struct in_addr aa; kk.s_addr = ntohl(k->s_addr & m->s_addr); aa.s_addr = ntohl(a->s_addr); if (kk.s_addr > aa.s_addr) return (1); if (kk.s_addr < aa.s_addr) return (-1); return (0); } /* * Return 0 if the address a (with mask m) matches address key * otherwise return 1 if a > key or -1 if a < key. It is assumed * that address a has been pre-masked out, we only need to mask key. */ static int match_addr_v6(const void *vkey, const void *ventry) { const struct sdaddr_v6 *k = vkey; const struct sdaddr_v6 *a = &((const struct sdentry_v6 *)ventry)->sda; const struct sdaddr_v6 *m = &((const struct sdentry_v6 *)ventry)->sdm; struct sdaddr_v6 kk; struct sdaddr_v6 aa; kk.addr32[0] = ntohl(k->addr32[0] & m->addr32[0]); kk.addr32[1] = ntohl(k->addr32[1] & m->addr32[1]); kk.addr32[2] = ntohl(k->addr32[2] & m->addr32[2]); kk.addr32[3] = ntohl(k->addr32[3] & m->addr32[3]); aa.addr32[0] = ntohl(a->addr32[0]); aa.addr32[1] = ntohl(a->addr32[1]); aa.addr32[2] = ntohl(a->addr32[2]); aa.addr32[3] = ntohl(a->addr32[3]); if (kk.addr32[0] > aa.addr32[0]) return (1); if (kk.addr32[0] < aa.addr32[0]) return (-1); if (kk.addr32[1] > aa.addr32[1]) return (1); if (kk.addr32[1] < aa.addr32[1]) return (-1); if (kk.addr32[2] > aa.addr32[2]) return (1); if (kk.addr32[2] < aa.addr32[2]) return (-1); if (kk.addr32[3] > aa.addr32[3]) return (1); if (kk.addr32[3] < aa.addr32[3]) return (-1); return (0); } #define grow_sdlist(sd, c, l) do { \ if (c == l) { \ struct sdlist **tmp; \ \ tmp = reallocarray(sd, l + 128, sizeof(struct sdlist *)); \ if (tmp == NULL) { \ /* \ * XXX out of memory - return what we have \ */ \ return (sdnew); \ } \ sd = tmp; \ l += 128; \ } \ } while (0) static struct sdlist ** sdl_lookup_v4(struct sdlist *sdl, struct in_addr *src) { int matches = 0; int sdnewlen = 0; struct sdlist **sdnew = NULL; while (sdl->tag != NULL) { if (bsearch(src, sdl->v4.addrs, sdl->v4.naddrs, sizeof(struct sdentry_v4), match_addr_v4) != NULL) { grow_sdlist(sdnew, matches, sdnewlen); sdnew[matches] = sdl; matches++; sdnew[matches] = NULL; break; } sdl++; } return (sdnew); } static struct sdlist ** sdl_lookup_v6(struct sdlist *sdl, struct sdaddr_v6 *src) { int matches = 0; int sdnewlen = 0; struct sdlist **sdnew = NULL; while (sdl->tag != NULL) { if (bsearch(src, sdl->v6.addrs, sdl->v6.naddrs, sizeof(struct sdentry_v6), match_addr_v6) != NULL) { grow_sdlist(sdnew, matches, sdnewlen); sdnew[matches] = sdl; matches++; sdnew[matches] = NULL; break; } sdl++; } return (sdnew); } /* * Given an address and address family * return list of pointers to matching nodes. or NULL if none. */ struct sdlist ** sdl_lookup(struct sdlist *head, int af, void *src) { if (head == NULL) return (NULL); switch (af) { case AF_INET: return (sdl_lookup_v4(head, src)); case AF_INET6: return (sdl_lookup_v6(head, src)); default: return (NULL); } } static int sdl_check_v4(struct sdlist *sdl, struct in_addr *src) { while (sdl->tag != NULL) { if (bsearch(src, sdl->v4.addrs, sdl->v4.naddrs, sizeof(struct sdentry_v4), match_addr_v4) != NULL) return (1); sdl++; } return (0); } static int sdl_check_v6(struct sdlist *sdl, struct sdaddr_v6 *src) { while (sdl->tag != NULL) { if (bsearch(src, sdl->v6.addrs, sdl->v6.naddrs, sizeof(struct sdentry_v6), match_addr_v6) != NULL) return (1); sdl++; } return (0); } /* * Given an address and address family * returns 1 if address is on a blacklist, else 0. */ int sdl_check(struct sdlist *head, int af, void *src) { if (head == NULL) return (0); switch (af) { case AF_INET: return (sdl_check_v4(head, src)); case AF_INET6: return (sdl_check_v6(head, src)); default: return (0); } } static void sdl_free(struct sdlist *sdl) { free(sdl->tag); free(sdl->string); free(sdl->v4.addrs); free(sdl->v6.addrs); sdl_clear(sdl); } static void sdl_clear(struct sdlist *sdl) { sdl->tag = NULL; sdl->string = NULL; sdl->v4.addrs = NULL; sdl->v4.naddrs = 0; sdl->v6.addrs = NULL; sdl->v6.naddrs = 0; }