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