1 /* $OpenBSD: pfutils.c,v 1.4 2006/06/14 14:49:46 ckuethe 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 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 = fd; 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 & POLLIN)){ 76 bzero(&cmd, l); 77 r = atomicio(read, pfpipe[0], &cmd, l); 78 79 if (r != l) 80 error("pf pipe error: %m"); 81 82 switch (cmd.type){ 83 case 'A': 84 pf_change_table(fd, 1, cmd.ip, abandoned_tab); 85 pf_kill_state(fd, cmd.ip); 86 break; 87 case 'C': 88 pf_change_table(fd, 0, cmd.ip, abandoned_tab); 89 pf_change_table(fd, 0, cmd.ip, changedmac_tab); 90 break; 91 case 'L': 92 pf_change_table(fd, 0, cmd.ip, abandoned_tab); 93 break; 94 default: 95 break; 96 } 97 } 98 } 99 /* not reached */ 100 exit(1); 101 } 102 103 /* inspired by ("stolen") from usr.sbin/authpf/authpf.c */ 104 void 105 pf_change_table(int fd, int op, struct in_addr ip, char *table) 106 { 107 struct pfioc_table io; 108 struct pfr_addr addr; 109 110 if (table == NULL) 111 return; 112 113 bzero(&io, sizeof(io)); 114 strlcpy(io.pfrio_table.pfrt_name, table, 115 sizeof(io.pfrio_table.pfrt_name)); 116 io.pfrio_buffer = &addr; 117 io.pfrio_esize = sizeof(addr); 118 io.pfrio_size = 1; 119 120 bzero(&addr, sizeof(addr)); 121 bcopy(&ip, &addr.pfra_ip4addr, 4); 122 addr.pfra_af = AF_INET; 123 addr.pfra_net = 32; 124 125 if (ioctl(fd, op ? DIOCRADDADDRS : DIOCRDELADDRS, &io) && 126 errno != ESRCH) { 127 warning( "DIOCR%sADDRS on table %s: %s", 128 op ? "ADD" : "DEL", table, strerror(errno)); 129 } 130 } 131 132 void 133 pf_kill_state(int fd, struct in_addr ip) 134 { 135 struct pfioc_state_kill psk; 136 struct pf_addr target; 137 138 bzero(&psk, sizeof(psk)); 139 bzero(&target, sizeof(target)); 140 141 bcopy(&ip.s_addr, &target.v4, 4); 142 psk.psk_af = AF_INET; 143 144 /* Kill all states from target */ 145 bcopy(&target, &psk.psk_src.addr.v.a.addr, 146 sizeof(psk.psk_src.addr.v.a.addr)); 147 memset(&psk.psk_src.addr.v.a.mask, 0xff, 148 sizeof(psk.psk_src.addr.v.a.mask)); 149 if (ioctl(fd, DIOCKILLSTATES, &psk)){ 150 warning("DIOCKILLSTATES failed (%s)", strerror(errno)); 151 } 152 153 /* Kill all states to target */ 154 bzero(&psk.psk_src, sizeof(psk.psk_src)); 155 bcopy(&target, &psk.psk_dst.addr.v.a.addr, 156 sizeof(psk.psk_dst.addr.v.a.addr)); 157 memset(&psk.psk_dst.addr.v.a.mask, 0xff, 158 sizeof(psk.psk_dst.addr.v.a.mask)); 159 if (ioctl(fd, DIOCKILLSTATES, &psk)){ 160 warning("DIOCKILLSTATES failed (%s)", strerror(errno)); 161 } 162 } 163 164 /* inspired by ("stolen") from usr.bin/ssh/atomicio.c */ 165 size_t 166 atomicio(ssize_t (*f) (int, void *, size_t), int fd, void *_s, size_t n) 167 { 168 char *s = _s; 169 size_t pos = 0; 170 ssize_t res; 171 172 while (n > pos) { 173 res = (f) (fd, s + pos, n - pos); 174 switch (res) { 175 case -1: 176 if (errno == EINTR || errno == EAGAIN) 177 continue; 178 return 0; 179 case 0: 180 errno = EPIPE; 181 return pos; 182 default: 183 pos += (size_t)res; 184 } 185 } 186 return (pos); 187 } 188 189 /* 190 * This function sends commands to the pf table handler. It will safely and 191 * silently return if the handler is unconfigured, therefore it can be called 192 * on all interesting lease events, whether or not the user actually wants to 193 * use the pf table feature. 194 */ 195 void 196 pfmsg(char c, struct lease *lp) 197 { 198 struct pf_cmd cmd; 199 200 if (gotpipe == 0) 201 return; 202 203 cmd.type = c; 204 bcopy(lp->ip_addr.iabuf, &cmd.ip.s_addr, 4); 205 206 switch(c){ 207 case 'A': /* address is being abandoned */ 208 if (abandoned_tab != NULL) 209 (void)atomicio(vwrite, pfpipe[1], &cmd, 210 sizeof(struct pf_cmd)); 211 break; 212 case 'C': /* IP moved to different ethernet address */ 213 if (changedmac_tab != NULL) 214 (void)atomicio(vwrite, pfpipe[1], &cmd, 215 sizeof(struct pf_cmd)); 216 break; 217 case 'L': /* Address is being leased (unabandoned) */ 218 if (abandoned_tab != NULL) 219 (void)atomicio(vwrite, pfpipe[1], &cmd, 220 sizeof(struct pf_cmd)); 221 break; 222 default: /* silently ignore unknown commands */ 223 break; 224 } 225 } 226