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