[BACK]Return to sdl.c CVS log [TXT][DIR] Up to [local] / src / libexec / spamd

File: [local] / src / libexec / spamd / sdl.c (download)

Revision 1.25, Mon Dec 26 20:06:43 2022 UTC (17 months, 1 week ago) by jmc
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, HEAD
Changes since 1.24: +3 -3 lines

spelling fixes; from paul tagliamonte

/*	$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 <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}