xref: /openbsd-src/usr.sbin/dhcpd/pfutils.c (revision dfafa18493d4f3f777c538be97961f5b4cc32c25)
1 /*	$OpenBSD: pfutils.c,v 1.4 2006/06/14 14:49:46 ckuethe 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/param.h>
21 #include <sys/socket.h>
22 #include <sys/time.h>
23 
24 #include <net/if.h>
25 #include <net/pfvar.h>
26 #include <arpa/inet.h>
27 
28 #include <ctype.h>
29 #include <err.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <poll.h>
33 #include <pwd.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 
39 #include "dhcpd.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 
47 __dead void
48 pftable_handler()
49 {
50 	struct pf_cmd cmd;
51 	struct pollfd pfd[1];
52 	int l, r, fd, nfds;
53 
54 	if ((fd = open(_PATH_DEV_PF, O_RDWR|O_NOFOLLOW, 0660)) == -1)
55 		error("can't open pf device: %m");
56 	if (chroot(_PATH_VAREMPTY) == -1)
57 		error("chroot %s: %m", _PATH_VAREMPTY);
58 	if (chdir("/") == -1)
59 		error("chdir(\"/\"): %m");
60 	if (setgroups(1, &pw->pw_gid) ||
61 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
62 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
63 		error("can't drop privileges: %m");
64 
65 	setproctitle("pf table handler");
66 	l = sizeof(struct pf_cmd);
67 
68 	for(;;){
69 		pfd[0].fd = fd;
70 		pfd[0].events = POLLIN;
71 		if ((nfds = poll(pfd, 1, -1)) == -1)
72 			if (errno != EINTR)
73 				error("poll: %m");
74 
75 		if (nfds > 0 && (pfd[0].revents & POLLIN)){
76 			bzero(&cmd, l);
77 			r = atomicio(read, pfpipe[0], &cmd, l);
78 
79 			if (r != l)
80 				error("pf pipe error: %m");
81 
82 			switch (cmd.type){
83 			case 'A':
84 				pf_change_table(fd, 1, cmd.ip, abandoned_tab);
85 				pf_kill_state(fd, cmd.ip);
86 				break;
87 			case 'C':
88 				pf_change_table(fd, 0, cmd.ip, abandoned_tab);
89 				pf_change_table(fd, 0, cmd.ip, changedmac_tab);
90 				break;
91 			case 'L':
92 				pf_change_table(fd, 0, cmd.ip, abandoned_tab);
93 				break;
94 			default:
95 				break;
96 			}
97 		}
98 	}
99 	/* not reached */
100 	exit(1);
101 }
102 
103 /* inspired by ("stolen") from usr.sbin/authpf/authpf.c */
104 void
105 pf_change_table(int fd, int op, struct in_addr ip, char *table)
106 {
107 	struct pfioc_table	io;
108 	struct pfr_addr		addr;
109 
110 	if (table == NULL)
111 		return;
112 
113 	bzero(&io, sizeof(io));
114 	strlcpy(io.pfrio_table.pfrt_name, table,
115 	    sizeof(io.pfrio_table.pfrt_name));
116 	io.pfrio_buffer = &addr;
117 	io.pfrio_esize = sizeof(addr);
118 	io.pfrio_size = 1;
119 
120 	bzero(&addr, sizeof(addr));
121 	bcopy(&ip, &addr.pfra_ip4addr, 4);
122 	addr.pfra_af = AF_INET;
123 	addr.pfra_net = 32;
124 
125 	if (ioctl(fd, op ? DIOCRADDADDRS : DIOCRDELADDRS, &io) &&
126 	    errno != ESRCH) {
127 		warning( "DIOCR%sADDRS on table %s: %s",
128 		    op ? "ADD" : "DEL", table, strerror(errno));
129 	}
130 }
131 
132 void
133 pf_kill_state(int fd, struct in_addr ip)
134 {
135 	struct pfioc_state_kill	psk;
136 	struct pf_addr target;
137 
138 	bzero(&psk, sizeof(psk));
139 	bzero(&target, sizeof(target));
140 
141 	bcopy(&ip.s_addr, &target.v4, 4);
142 	psk.psk_af = AF_INET;
143 
144 	/* Kill all states from target */
145 	bcopy(&target, &psk.psk_src.addr.v.a.addr,
146 	    sizeof(psk.psk_src.addr.v.a.addr));
147 	memset(&psk.psk_src.addr.v.a.mask, 0xff,
148 	    sizeof(psk.psk_src.addr.v.a.mask));
149 	if (ioctl(fd, DIOCKILLSTATES, &psk)){
150 		warning("DIOCKILLSTATES failed (%s)", strerror(errno));
151 	}
152 
153 	/* Kill all states to target */
154 	bzero(&psk.psk_src, sizeof(psk.psk_src));
155 	bcopy(&target, &psk.psk_dst.addr.v.a.addr,
156 	    sizeof(psk.psk_dst.addr.v.a.addr));
157 	memset(&psk.psk_dst.addr.v.a.mask, 0xff,
158 	    sizeof(psk.psk_dst.addr.v.a.mask));
159 	if (ioctl(fd, DIOCKILLSTATES, &psk)){
160 		warning("DIOCKILLSTATES failed (%s)", strerror(errno));
161 	}
162 }
163 
164 /* inspired by ("stolen") from usr.bin/ssh/atomicio.c */
165 size_t
166 atomicio(ssize_t (*f) (int, void *, size_t), int fd, void *_s, size_t n)
167 {
168 	char *s = _s;
169 	size_t pos = 0;
170 	ssize_t res;
171 
172 	while (n > pos) {
173 		res = (f) (fd, s + pos, n - pos);
174 		switch (res) {
175 		case -1:
176 			if (errno == EINTR || errno == EAGAIN)
177 				continue;
178 			return 0;
179 		case 0:
180 			errno = EPIPE;
181 			return pos;
182 		default:
183 			pos += (size_t)res;
184 		}
185 	}
186 	return (pos);
187 }
188 
189 /*
190  * This function sends commands to the pf table handler. It will safely and
191  * silently return if the handler is unconfigured, therefore it can be called
192  * on all interesting lease events, whether or not the user actually wants to
193  * use the pf table feature.
194  */
195 void
196 pfmsg(char c, struct lease *lp)
197 {
198 	struct pf_cmd cmd;
199 
200 	if (gotpipe == 0)
201 		return;
202 
203 	cmd.type = c;
204 	bcopy(lp->ip_addr.iabuf, &cmd.ip.s_addr, 4);
205 
206 	switch(c){
207 	case 'A': /* address is being abandoned */
208 		if (abandoned_tab != NULL)
209 			(void)atomicio(vwrite, pfpipe[1], &cmd,
210 			    sizeof(struct pf_cmd));
211 		break;
212 	case 'C': /* IP moved to different ethernet address */
213 		if (changedmac_tab != NULL)
214 			(void)atomicio(vwrite, pfpipe[1], &cmd,
215 			    sizeof(struct pf_cmd));
216 		break;
217 	case 'L': /* Address is being leased (unabandoned) */
218 		if (abandoned_tab != NULL)
219 			(void)atomicio(vwrite, pfpipe[1], &cmd,
220 			    sizeof(struct pf_cmd));
221 		break;
222 	default: /* silently ignore unknown commands */
223 		break;
224 	}
225 }
226