xref: /openbsd-src/usr.sbin/dhcpd/pfutils.c (revision 04fee6848cfe827855aa4ecd7f0a9da7d173e4d1)
1*04fee684Stb /*	$OpenBSD: pfutils.c,v 1.24 2023/02/08 08:20:53 tb Exp $ */
26f4dfa88Sckuethe /*
36f4dfa88Sckuethe  * Copyright (c) 2006 Chris Kuethe <ckuethe@openbsd.org>
46f4dfa88Sckuethe  *
56f4dfa88Sckuethe  * Permission to use, copy, modify, and distribute this software for any
66f4dfa88Sckuethe  * purpose with or without fee is hereby granted, provided that the above
76f4dfa88Sckuethe  * copyright notice and this permission notice appear in all copies.
86f4dfa88Sckuethe  *
96f4dfa88Sckuethe  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
106f4dfa88Sckuethe  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
116f4dfa88Sckuethe  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
126f4dfa88Sckuethe  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
136f4dfa88Sckuethe  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
146f4dfa88Sckuethe  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
156f4dfa88Sckuethe  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
166f4dfa88Sckuethe  */
176f4dfa88Sckuethe 
186f4dfa88Sckuethe #include <sys/types.h>
196f4dfa88Sckuethe #include <sys/ioctl.h>
206f4dfa88Sckuethe #include <sys/socket.h>
216f4dfa88Sckuethe 
2268928c43Sderaadt #include <netinet/in.h>
236f4dfa88Sckuethe #include <net/if.h>
246f4dfa88Sckuethe #include <net/pfvar.h>
256f4dfa88Sckuethe 
266f4dfa88Sckuethe #include <errno.h>
276f4dfa88Sckuethe #include <fcntl.h>
28837cddffSkrw #include <paths.h>
296f4dfa88Sckuethe #include <poll.h>
306f4dfa88Sckuethe #include <pwd.h>
316f4dfa88Sckuethe #include <stdio.h>
326f4dfa88Sckuethe #include <stdlib.h>
336f4dfa88Sckuethe #include <string.h>
346f4dfa88Sckuethe #include <unistd.h>
356f4dfa88Sckuethe 
36837cddffSkrw #include "dhcp.h"
37837cddffSkrw #include "tree.h"
386f4dfa88Sckuethe #include "dhcpd.h"
39c525a185Skrw #include "log.h"
406f4dfa88Sckuethe 
416f4dfa88Sckuethe extern struct passwd *pw;
426f4dfa88Sckuethe extern int pfpipe[2];
43dfafa184Sckuethe extern int gotpipe;
446f4dfa88Sckuethe extern char *abandoned_tab;
456f4dfa88Sckuethe extern char *changedmac_tab;
462cadf9d6Sckuethe extern char *leased_tab;
476f4dfa88Sckuethe 
486f4dfa88Sckuethe __dead void
pftable_handler(void)49*04fee684Stb pftable_handler(void)
506f4dfa88Sckuethe {
516f4dfa88Sckuethe 	struct pf_cmd cmd;
526f4dfa88Sckuethe 	struct pollfd pfd[1];
536f4dfa88Sckuethe 	int l, r, fd, nfds;
546f4dfa88Sckuethe 
55b7041c07Sderaadt 	if ((fd = open(_PATH_DEV_PF, O_RDWR|O_NOFOLLOW)) == -1)
56f4e4fe3aShenning 		fatal("can't open pf device");
57296c6648Smestre 
586f4dfa88Sckuethe 	if (setgroups(1, &pw->pw_gid) ||
596f4dfa88Sckuethe 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
606f4dfa88Sckuethe 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
61f4e4fe3aShenning 		fatal("can't drop privileges");
626f4dfa88Sckuethe 
63296c6648Smestre 	/* no filesystem visibility */
64296c6648Smestre 	if (unveil("/", "") == -1)
65bc5a8259Sbeck 		fatal("unveil /");
66296c6648Smestre 	if (unveil(NULL, NULL) == -1)
67296c6648Smestre 		fatal("unveil");
68296c6648Smestre 
696f4dfa88Sckuethe 	setproctitle("pf table handler");
706f4dfa88Sckuethe 	l = sizeof(struct pf_cmd);
716f4dfa88Sckuethe 
726f4dfa88Sckuethe 	for (;;) {
736bf824b0Stedu 		pfd[0].fd = pfpipe[0];
746f4dfa88Sckuethe 		pfd[0].events = POLLIN;
756f4dfa88Sckuethe 		if ((nfds = poll(pfd, 1, -1)) == -1)
766f4dfa88Sckuethe 			if (errno != EINTR)
770438cf0aSkrw 				log_warn("poll");
786f4dfa88Sckuethe 
7975976586Skrw 		if (nfds > 0 && (pfd[0].revents & POLLHUP))
80f4e4fe3aShenning 			fatalx("pf pipe closed");
8175976586Skrw 
826f4dfa88Sckuethe 		if (nfds > 0 && (pfd[0].revents & POLLIN)) {
83359ce2c3Smestre 			memset(&cmd, 0, l);
846f4dfa88Sckuethe 			r = atomicio(read, pfpipe[0], &cmd, l);
856f4dfa88Sckuethe 
866f4dfa88Sckuethe 			if (r != l)
87f4e4fe3aShenning 				fatalx("pf pipe error");
886f4dfa88Sckuethe 
896f4dfa88Sckuethe 			switch (cmd.type) {
906f4dfa88Sckuethe 			case 'A':
912cadf9d6Sckuethe 				/*
927259a944Sjmc 				 * When we abandon an address, we add it to
932cadf9d6Sckuethe 				 * the table of abandoned addresses, and remove
942cadf9d6Sckuethe 				 * it from the table of active leases.
952cadf9d6Sckuethe 				 */
966f4dfa88Sckuethe 				pf_change_table(fd, 1, cmd.ip, abandoned_tab);
972cadf9d6Sckuethe 				pf_change_table(fd, 0, cmd.ip, leased_tab);
986f4dfa88Sckuethe 				pf_kill_state(fd, cmd.ip);
996f4dfa88Sckuethe 				break;
1006f4dfa88Sckuethe 			case 'C':
1012cadf9d6Sckuethe 				/*
1022cadf9d6Sckuethe 				 * When the hardware address for an IP changes,
1032cadf9d6Sckuethe 				 * remove it from the table of abandoned
1042cadf9d6Sckuethe 				 * addresses, and from the table of overloaded
1052cadf9d6Sckuethe 				 * addresses.
1062cadf9d6Sckuethe 				 */
1076f4dfa88Sckuethe 				pf_change_table(fd, 0, cmd.ip, abandoned_tab);
1086f4dfa88Sckuethe 				pf_change_table(fd, 0, cmd.ip, changedmac_tab);
1096f4dfa88Sckuethe 				break;
1106f4dfa88Sckuethe 			case 'L':
1112cadf9d6Sckuethe 				/*
1122cadf9d6Sckuethe 				 * When a lease is granted or renewed, remove
1132cadf9d6Sckuethe 				 * it from the table of abandoned addresses,
1142cadf9d6Sckuethe 				 * and ensure it is in the table of active
1152cadf9d6Sckuethe 				 * leases.
1162cadf9d6Sckuethe 				 */
1176f4dfa88Sckuethe 				pf_change_table(fd, 0, cmd.ip, abandoned_tab);
1182cadf9d6Sckuethe 				pf_change_table(fd, 1, cmd.ip, leased_tab);
1192cadf9d6Sckuethe 				break;
1202cadf9d6Sckuethe 			case 'R':
1212cadf9d6Sckuethe 				/*
1222cadf9d6Sckuethe 				 * When we release or expire a lease, remove
1232cadf9d6Sckuethe 				 * it from the table of active leases. As long
1242cadf9d6Sckuethe 				 * as dhcpd doesn't abandon the address, no
1252cadf9d6Sckuethe 				 * further action is required.
1262cadf9d6Sckuethe 				 */
1272cadf9d6Sckuethe 				pf_change_table(fd, 0, cmd.ip, leased_tab);
1286f4dfa88Sckuethe 				break;
1296f4dfa88Sckuethe 			default:
1306f4dfa88Sckuethe 				break;
1316f4dfa88Sckuethe 			}
1326f4dfa88Sckuethe 		}
1336f4dfa88Sckuethe 	}
1346f4dfa88Sckuethe 	/* not reached */
1356f4dfa88Sckuethe 	exit(1);
1366f4dfa88Sckuethe }
1376f4dfa88Sckuethe 
1386f4dfa88Sckuethe /* inspired by ("stolen") from usr.sbin/authpf/authpf.c */
1396f4dfa88Sckuethe void
pf_change_table(int fd,int op,struct in_addr ip,char * table)1406f4dfa88Sckuethe pf_change_table(int fd, int op, struct in_addr ip, char *table)
1416f4dfa88Sckuethe {
1426f4dfa88Sckuethe 	struct pfioc_table	io;
1436f4dfa88Sckuethe 	struct pfr_addr		addr;
1446f4dfa88Sckuethe 
14508a8b7bdSckuethe 	if (table == NULL)
14608a8b7bdSckuethe 		return;
14708a8b7bdSckuethe 
148359ce2c3Smestre 	memset(&io, 0, sizeof(io));
1496f4dfa88Sckuethe 	strlcpy(io.pfrio_table.pfrt_name, table,
1506f4dfa88Sckuethe 	    sizeof(io.pfrio_table.pfrt_name));
1516f4dfa88Sckuethe 	io.pfrio_buffer = &addr;
1526f4dfa88Sckuethe 	io.pfrio_esize = sizeof(addr);
1536f4dfa88Sckuethe 	io.pfrio_size = 1;
1546f4dfa88Sckuethe 
155359ce2c3Smestre 	memset(&addr, 0, sizeof(addr));
1561674d7d2Skrw 	memcpy(&addr.pfra_ip4addr, &ip, 4);
1576f4dfa88Sckuethe 	addr.pfra_af = AF_INET;
1586f4dfa88Sckuethe 	addr.pfra_net = 32;
1596f4dfa88Sckuethe 
160df69c215Sderaadt 	if (ioctl(fd, op ? DIOCRADDADDRS : DIOCRDELADDRS, &io) == -1 &&
1616f4dfa88Sckuethe 	    errno != ESRCH) {
162a76b277aSkrw 		log_warn( "DIOCR%sADDRS on table %s", op ? "ADD" : "DEL",
163a76b277aSkrw 		    table);
1646f4dfa88Sckuethe 	}
1656f4dfa88Sckuethe }
1666f4dfa88Sckuethe 
1676f4dfa88Sckuethe void
pf_kill_state(int fd,struct in_addr ip)1686f4dfa88Sckuethe pf_kill_state(int fd, struct in_addr ip)
1696f4dfa88Sckuethe {
1706f4dfa88Sckuethe 	struct pfioc_state_kill	psk;
1716f4dfa88Sckuethe 	struct pf_addr target;
1726f4dfa88Sckuethe 
173359ce2c3Smestre 	memset(&psk, 0, sizeof(psk));
174359ce2c3Smestre 	memset(&target, 0, sizeof(target));
1756f4dfa88Sckuethe 
1761674d7d2Skrw 	memcpy(&target.v4, &ip.s_addr, 4);
1776f4dfa88Sckuethe 	psk.psk_af = AF_INET;
1786f4dfa88Sckuethe 
1796f4dfa88Sckuethe 	/* Kill all states from target */
1801674d7d2Skrw 	memcpy(&psk.psk_src.addr.v.a.addr, &target,
1816f4dfa88Sckuethe 	    sizeof(psk.psk_src.addr.v.a.addr));
1826f4dfa88Sckuethe 	memset(&psk.psk_src.addr.v.a.mask, 0xff,
1836f4dfa88Sckuethe 	    sizeof(psk.psk_src.addr.v.a.mask));
184df69c215Sderaadt 	if (ioctl(fd, DIOCKILLSTATES, &psk) == -1) {
185a76b277aSkrw 		log_warn("DIOCKILLSTATES failed");
1866f4dfa88Sckuethe 	}
1876f4dfa88Sckuethe 
1886f4dfa88Sckuethe 	/* Kill all states to target */
189359ce2c3Smestre 	memset(&psk.psk_src, 0, sizeof(psk.psk_src));
1901674d7d2Skrw 	memcpy(&psk.psk_dst.addr.v.a.addr, &target,
1916f4dfa88Sckuethe 	    sizeof(psk.psk_dst.addr.v.a.addr));
1926f4dfa88Sckuethe 	memset(&psk.psk_dst.addr.v.a.mask, 0xff,
1936f4dfa88Sckuethe 	    sizeof(psk.psk_dst.addr.v.a.mask));
194df69c215Sderaadt 	if (ioctl(fd, DIOCKILLSTATES, &psk) == -1) {
195a76b277aSkrw 		log_warn("DIOCKILLSTATES failed");
1966f4dfa88Sckuethe 	}
1976f4dfa88Sckuethe }
1986f4dfa88Sckuethe 
1996f4dfa88Sckuethe /* inspired by ("stolen") from usr.bin/ssh/atomicio.c */
2006f4dfa88Sckuethe size_t
atomicio(ssize_t (* f)(int,void *,size_t),int fd,void * _s,size_t n)2016f4dfa88Sckuethe atomicio(ssize_t (*f) (int, void *, size_t), int fd, void *_s, size_t n)
2026f4dfa88Sckuethe {
2036f4dfa88Sckuethe 	char *s = _s;
2046f4dfa88Sckuethe 	size_t pos = 0;
2056f4dfa88Sckuethe 	ssize_t res;
2066f4dfa88Sckuethe 
2076f4dfa88Sckuethe 	while (n > pos) {
2086f4dfa88Sckuethe 		res = (f) (fd, s + pos, n - pos);
2096f4dfa88Sckuethe 		switch (res) {
2106f4dfa88Sckuethe 		case -1:
2116f4dfa88Sckuethe 			if (errno == EINTR || errno == EAGAIN)
2126f4dfa88Sckuethe 				continue;
2136f4dfa88Sckuethe 			return 0;
2146f4dfa88Sckuethe 		case 0:
2156f4dfa88Sckuethe 			errno = EPIPE;
2166f4dfa88Sckuethe 			return pos;
2176f4dfa88Sckuethe 		default:
2186f4dfa88Sckuethe 			pos += (size_t)res;
2196f4dfa88Sckuethe 		}
2206f4dfa88Sckuethe 	}
2216f4dfa88Sckuethe 	return (pos);
2226f4dfa88Sckuethe }
223dfafa184Sckuethe 
224dfafa184Sckuethe /*
225dfafa184Sckuethe  * This function sends commands to the pf table handler. It will safely and
226dfafa184Sckuethe  * silently return if the handler is unconfigured, therefore it can be called
227dfafa184Sckuethe  * on all interesting lease events, whether or not the user actually wants to
228dfafa184Sckuethe  * use the pf table feature.
229dfafa184Sckuethe  */
230dfafa184Sckuethe void
pfmsg(char c,struct lease * lp)231dfafa184Sckuethe pfmsg(char c, struct lease *lp)
232dfafa184Sckuethe {
233dfafa184Sckuethe 	struct pf_cmd cmd;
234dfafa184Sckuethe 
235dfafa184Sckuethe 	if (gotpipe == 0)
236dfafa184Sckuethe 		return;
237dfafa184Sckuethe 
238dfafa184Sckuethe 	cmd.type = c;
2391674d7d2Skrw 	memcpy(&cmd.ip.s_addr, lp->ip_addr.iabuf, 4);
240dfafa184Sckuethe 
241dfafa184Sckuethe 	switch (c) {
242dfafa184Sckuethe 	case 'A': /* address is being abandoned */
243e669f95eSckuethe 		/* FALLTHROUGH */
244dfafa184Sckuethe 	case 'C': /* IP moved to different ethernet address */
245e669f95eSckuethe 		/* FALLTHROUGH */
246dfafa184Sckuethe 	case 'L': /* Address is being leased (unabandoned) */
247e669f95eSckuethe 		/* FALLTHROUGH */
2482cadf9d6Sckuethe 	case 'R': /* Address is being released or lease has expired */
2492cadf9d6Sckuethe 		(void)atomicio(vwrite, pfpipe[1], &cmd,
2502cadf9d6Sckuethe 		    sizeof(struct pf_cmd));
2512cadf9d6Sckuethe 		break;
252dfafa184Sckuethe 	default: /* silently ignore unknown commands */
253dfafa184Sckuethe 		break;
254dfafa184Sckuethe 	}
255dfafa184Sckuethe }
256