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