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