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