xref: /openbsd-src/usr.sbin/dhcpd/pfutils.c (revision c90a81c56dcebd6a1b73fe4aff9b03385b8e63b3)
1 /*	$OpenBSD: pfutils.c,v 1.19 2018/12/07 12:52:47 henning 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
49 pftable_handler()
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, 0660)) == -1)
56 		fatal("can't open pf device");
57 	if (chroot(_PATH_VAREMPTY) == -1)
58 		fatal("chroot %s", _PATH_VAREMPTY);
59 	if (chdir("/") == -1)
60 		fatal("chdir(\"/\")");
61 	if (setgroups(1, &pw->pw_gid) ||
62 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
63 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
64 		fatal("can't drop privileges");
65 
66 	setproctitle("pf table handler");
67 	l = sizeof(struct pf_cmd);
68 
69 	for (;;) {
70 		pfd[0].fd = pfpipe[0];
71 		pfd[0].events = POLLIN;
72 		if ((nfds = poll(pfd, 1, -1)) == -1)
73 			if (errno != EINTR)
74 				log_warn("poll");
75 
76 		if (nfds > 0 && (pfd[0].revents & POLLHUP))
77 			fatalx("pf pipe closed");
78 
79 		if (nfds > 0 && (pfd[0].revents & POLLIN)) {
80 			memset(&cmd, 0, l);
81 			r = atomicio(read, pfpipe[0], &cmd, l);
82 
83 			if (r != l)
84 				fatalx("pf pipe error");
85 
86 			switch (cmd.type) {
87 			case 'A':
88 				/*
89 				 * When we abandon an address, we add it to
90 				 * the table of abandoned addresses, and remove
91 				 * it from the table of active leases.
92 				 */
93 				pf_change_table(fd, 1, cmd.ip, abandoned_tab);
94 				pf_change_table(fd, 0, cmd.ip, leased_tab);
95 				pf_kill_state(fd, cmd.ip);
96 				break;
97 			case 'C':
98 				/*
99 				 * When the hardware address for an IP changes,
100 				 * remove it from the table of abandoned
101 				 * addresses, and from the table of overloaded
102 				 * addresses.
103 				 */
104 				pf_change_table(fd, 0, cmd.ip, abandoned_tab);
105 				pf_change_table(fd, 0, cmd.ip, changedmac_tab);
106 				break;
107 			case 'L':
108 				/*
109 				 * When a lease is granted or renewed, remove
110 				 * it from the table of abandoned addresses,
111 				 * and ensure it is in the table of active
112 				 * leases.
113 				 */
114 				pf_change_table(fd, 0, cmd.ip, abandoned_tab);
115 				pf_change_table(fd, 1, cmd.ip, leased_tab);
116 				break;
117 			case 'R':
118 				/*
119 				 * When we release or expire a lease, remove
120 				 * it from the table of active leases. As long
121 				 * as dhcpd doesn't abandon the address, no
122 				 * further action is required.
123 				 */
124 				pf_change_table(fd, 0, cmd.ip, leased_tab);
125 				break;
126 			default:
127 				break;
128 			}
129 		}
130 	}
131 	/* not reached */
132 	exit(1);
133 }
134 
135 /* inspired by ("stolen") from usr.sbin/authpf/authpf.c */
136 void
137 pf_change_table(int fd, int op, struct in_addr ip, char *table)
138 {
139 	struct pfioc_table	io;
140 	struct pfr_addr		addr;
141 
142 	if (table == NULL)
143 		return;
144 
145 	memset(&io, 0, sizeof(io));
146 	strlcpy(io.pfrio_table.pfrt_name, table,
147 	    sizeof(io.pfrio_table.pfrt_name));
148 	io.pfrio_buffer = &addr;
149 	io.pfrio_esize = sizeof(addr);
150 	io.pfrio_size = 1;
151 
152 	memset(&addr, 0, sizeof(addr));
153 	memcpy(&addr.pfra_ip4addr, &ip, 4);
154 	addr.pfra_af = AF_INET;
155 	addr.pfra_net = 32;
156 
157 	if (ioctl(fd, op ? DIOCRADDADDRS : DIOCRDELADDRS, &io) &&
158 	    errno != ESRCH) {
159 		log_warn( "DIOCR%sADDRS on table %s", op ? "ADD" : "DEL",
160 		    table);
161 	}
162 }
163 
164 void
165 pf_kill_state(int fd, struct in_addr ip)
166 {
167 	struct pfioc_state_kill	psk;
168 	struct pf_addr target;
169 
170 	memset(&psk, 0, sizeof(psk));
171 	memset(&target, 0, sizeof(target));
172 
173 	memcpy(&target.v4, &ip.s_addr, 4);
174 	psk.psk_af = AF_INET;
175 
176 	/* Kill all states from target */
177 	memcpy(&psk.psk_src.addr.v.a.addr, &target,
178 	    sizeof(psk.psk_src.addr.v.a.addr));
179 	memset(&psk.psk_src.addr.v.a.mask, 0xff,
180 	    sizeof(psk.psk_src.addr.v.a.mask));
181 	if (ioctl(fd, DIOCKILLSTATES, &psk)) {
182 		log_warn("DIOCKILLSTATES failed");
183 	}
184 
185 	/* Kill all states to target */
186 	memset(&psk.psk_src, 0, sizeof(psk.psk_src));
187 	memcpy(&psk.psk_dst.addr.v.a.addr, &target,
188 	    sizeof(psk.psk_dst.addr.v.a.addr));
189 	memset(&psk.psk_dst.addr.v.a.mask, 0xff,
190 	    sizeof(psk.psk_dst.addr.v.a.mask));
191 	if (ioctl(fd, DIOCKILLSTATES, &psk)) {
192 		log_warn("DIOCKILLSTATES failed");
193 	}
194 }
195 
196 /* inspired by ("stolen") from usr.bin/ssh/atomicio.c */
197 size_t
198 atomicio(ssize_t (*f) (int, void *, size_t), int fd, void *_s, size_t n)
199 {
200 	char *s = _s;
201 	size_t pos = 0;
202 	ssize_t res;
203 
204 	while (n > pos) {
205 		res = (f) (fd, s + pos, n - pos);
206 		switch (res) {
207 		case -1:
208 			if (errno == EINTR || errno == EAGAIN)
209 				continue;
210 			return 0;
211 		case 0:
212 			errno = EPIPE;
213 			return pos;
214 		default:
215 			pos += (size_t)res;
216 		}
217 	}
218 	return (pos);
219 }
220 
221 /*
222  * This function sends commands to the pf table handler. It will safely and
223  * silently return if the handler is unconfigured, therefore it can be called
224  * on all interesting lease events, whether or not the user actually wants to
225  * use the pf table feature.
226  */
227 void
228 pfmsg(char c, struct lease *lp)
229 {
230 	struct pf_cmd cmd;
231 
232 	if (gotpipe == 0)
233 		return;
234 
235 	cmd.type = c;
236 	memcpy(&cmd.ip.s_addr, lp->ip_addr.iabuf, 4);
237 
238 	switch (c) {
239 	case 'A': /* address is being abandoned */
240 		/* FALLTHROUGH */
241 	case 'C': /* IP moved to different ethernet address */
242 		/* FALLTHROUGH */
243 	case 'L': /* Address is being leased (unabandoned) */
244 		/* FALLTHROUGH */
245 	case 'R': /* Address is being released or lease has expired */
246 		(void)atomicio(vwrite, pfpipe[1], &cmd,
247 		    sizeof(struct pf_cmd));
248 		break;
249 	default: /* silently ignore unknown commands */
250 		break;
251 	}
252 }
253