1 /* $OpenBSD: pfutils.c,v 1.24 2023/02/08 08:20:53 tb Exp $ */
2 /*
3 * Copyright (c) 2006 Chris Kuethe <ckuethe@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <sys/types.h>
19 #include <sys/ioctl.h>
20 #include <sys/socket.h>
21
22 #include <netinet/in.h>
23 #include <net/if.h>
24 #include <net/pfvar.h>
25
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <paths.h>
29 #include <poll.h>
30 #include <pwd.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35
36 #include "dhcp.h"
37 #include "tree.h"
38 #include "dhcpd.h"
39 #include "log.h"
40
41 extern struct passwd *pw;
42 extern int pfpipe[2];
43 extern int gotpipe;
44 extern char *abandoned_tab;
45 extern char *changedmac_tab;
46 extern char *leased_tab;
47
48 __dead void
pftable_handler(void)49 pftable_handler(void)
50 {
51 struct pf_cmd cmd;
52 struct pollfd pfd[1];
53 int l, r, fd, nfds;
54
55 if ((fd = open(_PATH_DEV_PF, O_RDWR|O_NOFOLLOW)) == -1)
56 fatal("can't open pf device");
57
58 if (setgroups(1, &pw->pw_gid) ||
59 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
60 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
61 fatal("can't drop privileges");
62
63 /* no filesystem visibility */
64 if (unveil("/", "") == -1)
65 fatal("unveil /");
66 if (unveil(NULL, NULL) == -1)
67 fatal("unveil");
68
69 setproctitle("pf table handler");
70 l = sizeof(struct pf_cmd);
71
72 for (;;) {
73 pfd[0].fd = pfpipe[0];
74 pfd[0].events = POLLIN;
75 if ((nfds = poll(pfd, 1, -1)) == -1)
76 if (errno != EINTR)
77 log_warn("poll");
78
79 if (nfds > 0 && (pfd[0].revents & POLLHUP))
80 fatalx("pf pipe closed");
81
82 if (nfds > 0 && (pfd[0].revents & POLLIN)) {
83 memset(&cmd, 0, l);
84 r = atomicio(read, pfpipe[0], &cmd, l);
85
86 if (r != l)
87 fatalx("pf pipe error");
88
89 switch (cmd.type) {
90 case 'A':
91 /*
92 * When we abandon an address, we add it to
93 * the table of abandoned addresses, and remove
94 * it from the table of active leases.
95 */
96 pf_change_table(fd, 1, cmd.ip, abandoned_tab);
97 pf_change_table(fd, 0, cmd.ip, leased_tab);
98 pf_kill_state(fd, cmd.ip);
99 break;
100 case 'C':
101 /*
102 * When the hardware address for an IP changes,
103 * remove it from the table of abandoned
104 * addresses, and from the table of overloaded
105 * addresses.
106 */
107 pf_change_table(fd, 0, cmd.ip, abandoned_tab);
108 pf_change_table(fd, 0, cmd.ip, changedmac_tab);
109 break;
110 case 'L':
111 /*
112 * When a lease is granted or renewed, remove
113 * it from the table of abandoned addresses,
114 * and ensure it is in the table of active
115 * leases.
116 */
117 pf_change_table(fd, 0, cmd.ip, abandoned_tab);
118 pf_change_table(fd, 1, cmd.ip, leased_tab);
119 break;
120 case 'R':
121 /*
122 * When we release or expire a lease, remove
123 * it from the table of active leases. As long
124 * as dhcpd doesn't abandon the address, no
125 * further action is required.
126 */
127 pf_change_table(fd, 0, cmd.ip, leased_tab);
128 break;
129 default:
130 break;
131 }
132 }
133 }
134 /* not reached */
135 exit(1);
136 }
137
138 /* inspired by ("stolen") from usr.sbin/authpf/authpf.c */
139 void
pf_change_table(int fd,int op,struct in_addr ip,char * table)140 pf_change_table(int fd, int op, struct in_addr ip, char *table)
141 {
142 struct pfioc_table io;
143 struct pfr_addr addr;
144
145 if (table == NULL)
146 return;
147
148 memset(&io, 0, sizeof(io));
149 strlcpy(io.pfrio_table.pfrt_name, table,
150 sizeof(io.pfrio_table.pfrt_name));
151 io.pfrio_buffer = &addr;
152 io.pfrio_esize = sizeof(addr);
153 io.pfrio_size = 1;
154
155 memset(&addr, 0, sizeof(addr));
156 memcpy(&addr.pfra_ip4addr, &ip, 4);
157 addr.pfra_af = AF_INET;
158 addr.pfra_net = 32;
159
160 if (ioctl(fd, op ? DIOCRADDADDRS : DIOCRDELADDRS, &io) == -1 &&
161 errno != ESRCH) {
162 log_warn( "DIOCR%sADDRS on table %s", op ? "ADD" : "DEL",
163 table);
164 }
165 }
166
167 void
pf_kill_state(int fd,struct in_addr ip)168 pf_kill_state(int fd, struct in_addr ip)
169 {
170 struct pfioc_state_kill psk;
171 struct pf_addr target;
172
173 memset(&psk, 0, sizeof(psk));
174 memset(&target, 0, sizeof(target));
175
176 memcpy(&target.v4, &ip.s_addr, 4);
177 psk.psk_af = AF_INET;
178
179 /* Kill all states from target */
180 memcpy(&psk.psk_src.addr.v.a.addr, &target,
181 sizeof(psk.psk_src.addr.v.a.addr));
182 memset(&psk.psk_src.addr.v.a.mask, 0xff,
183 sizeof(psk.psk_src.addr.v.a.mask));
184 if (ioctl(fd, DIOCKILLSTATES, &psk) == -1) {
185 log_warn("DIOCKILLSTATES failed");
186 }
187
188 /* Kill all states to target */
189 memset(&psk.psk_src, 0, sizeof(psk.psk_src));
190 memcpy(&psk.psk_dst.addr.v.a.addr, &target,
191 sizeof(psk.psk_dst.addr.v.a.addr));
192 memset(&psk.psk_dst.addr.v.a.mask, 0xff,
193 sizeof(psk.psk_dst.addr.v.a.mask));
194 if (ioctl(fd, DIOCKILLSTATES, &psk) == -1) {
195 log_warn("DIOCKILLSTATES failed");
196 }
197 }
198
199 /* inspired by ("stolen") from usr.bin/ssh/atomicio.c */
200 size_t
atomicio(ssize_t (* f)(int,void *,size_t),int fd,void * _s,size_t n)201 atomicio(ssize_t (*f) (int, void *, size_t), int fd, void *_s, size_t n)
202 {
203 char *s = _s;
204 size_t pos = 0;
205 ssize_t res;
206
207 while (n > pos) {
208 res = (f) (fd, s + pos, n - pos);
209 switch (res) {
210 case -1:
211 if (errno == EINTR || errno == EAGAIN)
212 continue;
213 return 0;
214 case 0:
215 errno = EPIPE;
216 return pos;
217 default:
218 pos += (size_t)res;
219 }
220 }
221 return (pos);
222 }
223
224 /*
225 * This function sends commands to the pf table handler. It will safely and
226 * silently return if the handler is unconfigured, therefore it can be called
227 * on all interesting lease events, whether or not the user actually wants to
228 * use the pf table feature.
229 */
230 void
pfmsg(char c,struct lease * lp)231 pfmsg(char c, struct lease *lp)
232 {
233 struct pf_cmd cmd;
234
235 if (gotpipe == 0)
236 return;
237
238 cmd.type = c;
239 memcpy(&cmd.ip.s_addr, lp->ip_addr.iabuf, 4);
240
241 switch (c) {
242 case 'A': /* address is being abandoned */
243 /* FALLTHROUGH */
244 case 'C': /* IP moved to different ethernet address */
245 /* FALLTHROUGH */
246 case 'L': /* Address is being leased (unabandoned) */
247 /* FALLTHROUGH */
248 case 'R': /* Address is being released or lease has expired */
249 (void)atomicio(vwrite, pfpipe[1], &cmd,
250 sizeof(struct pf_cmd));
251 break;
252 default: /* silently ignore unknown commands */
253 break;
254 }
255 }
256