xref: /openbsd-src/usr.sbin/dhcpd/pfutils.c (revision 6f4dfa88dedd19845451ccc842274bc706cae8a9)
1 /*
2  * Copyright (c) 2006 Chris Kuethe <ckuethe@openbsd.org>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include <sys/types.h>
18 #include <sys/ioctl.h>
19 #include <sys/param.h>
20 #include <sys/socket.h>
21 #include <sys/time.h>
22 
23 #include <net/if.h>
24 #include <net/pfvar.h>
25 #include <arpa/inet.h>
26 
27 #include <ctype.h>
28 #include <err.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <poll.h>
32 #include <pwd.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 
38 #include "dhcpd.h"
39 
40 extern struct passwd *pw;
41 extern int pfpipe[2];
42 extern char *abandoned_tab;
43 extern char *changedmac_tab;
44 
45 __dead void
46 pftable_handler()
47 {
48 	struct pf_cmd cmd;
49 	struct pollfd pfd[1];
50 	int l, r, fd, nfds;
51 
52 	if ((fd = open(_PATH_DEV_PF, O_RDWR|O_NOFOLLOW, 0660)) == -1)
53 		error("can't open pf device: %m");
54 	if (chroot(_PATH_VAREMPTY) == -1)
55 		error("chroot %s: %m", _PATH_VAREMPTY);
56 	if (chdir("/") == -1)
57 		error("chdir(\"/\"): %m");
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 		error("can't drop privileges: %m");
62 
63 	setproctitle("pf table handler");
64 	l = sizeof(struct pf_cmd);
65 
66 	for(;;){
67 		pfd[0].fd = fd;
68 		pfd[0].events = POLLIN;
69 		if ((nfds = poll(pfd, 1, -1)) == -1)
70 			if (errno != EINTR)
71 				error("poll: %m");
72 
73 		if (nfds > 0 && (pfd[0].revents & POLLIN)){
74 			bzero(&cmd, l);
75 			r = atomicio(read, pfpipe[0], &cmd, l);
76 
77 			if (r != l)
78 				error("pf pipe error: %m");
79 
80 			switch (cmd.type){
81 			case 'A':
82 				pf_change_table(fd, 1, cmd.ip, abandoned_tab);
83 				pf_kill_state(fd, cmd.ip);
84 				break;
85 			case 'C':
86 				pf_change_table(fd, 0, cmd.ip, abandoned_tab);
87 				pf_change_table(fd, 0, cmd.ip, changedmac_tab);
88 				break;
89 			case 'L':
90 				pf_change_table(fd, 0, cmd.ip, abandoned_tab);
91 				break;
92 			default:
93 				break;
94 			}
95 		}
96 	}
97 	/* not reached */
98 	exit(1);
99 }
100 
101 /* inspired by ("stolen") from usr.sbin/authpf/authpf.c */
102 void
103 pf_change_table(int fd, int op, struct in_addr ip, char *table)
104 {
105 	struct pfioc_table	io;
106 	struct pfr_addr		addr;
107 
108 	bzero(&io, sizeof(io));
109 	strlcpy(io.pfrio_table.pfrt_name, table,
110 	    sizeof(io.pfrio_table.pfrt_name));
111 	io.pfrio_buffer = &addr;
112 	io.pfrio_esize = sizeof(addr);
113 	io.pfrio_size = 1;
114 
115 	bzero(&addr, sizeof(addr));
116 	bcopy(&ip, &addr.pfra_ip4addr, 4);
117 	addr.pfra_af = AF_INET;
118 	addr.pfra_net = 32;
119 
120 	if (ioctl(fd, op ? DIOCRADDADDRS : DIOCRDELADDRS, &io) &&
121 	    errno != ESRCH) {
122 		warning( "DIOCR%sADDRS on table %s: %s",
123 		    op ? "ADD" : "DEL", table, strerror(errno));
124 	}
125 }
126 
127 void
128 pf_kill_state(int fd, struct in_addr ip)
129 {
130 	struct pfioc_state_kill	psk;
131 	struct pf_addr target;
132 
133 	bzero(&psk, sizeof(psk));
134 	bzero(&target, sizeof(target));
135 
136 	bcopy(&ip.s_addr, &target.v4, 4);
137 	psk.psk_af = AF_INET;
138 
139 	/* Kill all states from target */
140 	bcopy(&target, &psk.psk_src.addr.v.a.addr,
141 	    sizeof(psk.psk_src.addr.v.a.addr));
142 	memset(&psk.psk_src.addr.v.a.mask, 0xff,
143 	    sizeof(psk.psk_src.addr.v.a.mask));
144 	if (ioctl(fd, DIOCKILLSTATES, &psk)){
145 		warning("DIOCKILLSTATES failed (%s)", strerror(errno));
146 	}
147 
148 	/* Kill all states to target */
149 	bzero(&psk.psk_src, sizeof(psk.psk_src));
150 	bcopy(&target, &psk.psk_dst.addr.v.a.addr,
151 	    sizeof(psk.psk_dst.addr.v.a.addr));
152 	memset(&psk.psk_dst.addr.v.a.mask, 0xff,
153 	    sizeof(psk.psk_dst.addr.v.a.mask));
154 	if (ioctl(fd, DIOCKILLSTATES, &psk)){
155 		warning("DIOCKILLSTATES failed (%s)", strerror(errno));
156 	}
157 }
158 
159 /* inspired by ("stolen") from usr.bin/ssh/atomicio.c */
160 size_t
161 atomicio(ssize_t (*f) (int, void *, size_t), int fd, void *_s, size_t n)
162 {
163 	char *s = _s;
164 	size_t pos = 0;
165 	ssize_t res;
166 
167 	while (n > pos) {
168 		res = (f) (fd, s + pos, n - pos);
169 		switch (res) {
170 		case -1:
171 			if (errno == EINTR || errno == EAGAIN)
172 				continue;
173 			return 0;
174 		case 0:
175 			errno = EPIPE;
176 			return pos;
177 		default:
178 			pos += (size_t)res;
179 		}
180 	}
181 	return (pos);
182 }
183