1 /* $OpenBSD: dhcpd.c,v 1.59 2023/10/06 05:31:54 jmc Exp $ */ 2 3 /* 4 * Copyright (c) 2004 Henning Brauer <henning@cvs.openbsd.org> 5 * Copyright (c) 1995, 1996, 1997, 1998, 1999 6 * The Internet Software Consortium. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. Neither the name of The Internet Software Consortium nor the names 18 * of its contributors may be used to endorse or promote products derived 19 * from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND 22 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 23 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 24 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR 26 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 29 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 30 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 * 35 * This software has been written for the Internet Software Consortium 36 * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie 37 * Enterprises. To learn more about the Internet Software Consortium, 38 * see ``http://www.vix.com/isc''. To learn more about Vixie 39 * Enterprises, see ``http://www.vix.com''. 40 */ 41 42 #include <sys/types.h> 43 #include <sys/socket.h> 44 45 #include <net/if.h> 46 47 #include <arpa/inet.h> 48 49 #include <err.h> 50 #include <netdb.h> 51 #include <pwd.h> 52 #include <stdio.h> 53 #include <stdlib.h> 54 #include <string.h> 55 #include <syslog.h> 56 #include <time.h> 57 #include <unistd.h> 58 59 #include "dhcp.h" 60 #include "tree.h" 61 #include "dhcpd.h" 62 #include "log.h" 63 #include "sync.h" 64 65 66 __dead void usage(void); 67 68 time_t cur_time, last_scan; 69 struct group root_group; 70 71 u_int16_t server_port; 72 u_int16_t client_port; 73 74 struct passwd *pw; 75 int log_priority; 76 int pfpipe[2]; 77 int gotpipe = 0; 78 int syncrecv; 79 int syncsend; 80 pid_t pfproc_pid = -1; 81 char *path_dhcpd_conf = _PATH_DHCPD_CONF; 82 char *path_dhcpd_db = _PATH_DHCPD_DB; 83 char *abandoned_tab = NULL; 84 char *changedmac_tab = NULL; 85 char *leased_tab = NULL; 86 87 int 88 main(int argc, char *argv[]) 89 { 90 int ch, cftest = 0, rdomain = -1, udpsockmode = 0; 91 int debug = 0, verbose = 0; 92 char *sync_iface = NULL; 93 char *sync_baddr = NULL; 94 u_short sync_port = 0; 95 struct servent *ent; 96 struct in_addr udpaddr; 97 98 log_init(1, LOG_DAEMON); /* log to stderr until daemonized */ 99 log_setverbose(1); 100 101 opterr = 0; 102 while ((ch = getopt(argc, argv, "A:C:L:c:dfl:nu::vY:y:")) != -1) 103 switch (ch) { 104 case 'Y': 105 syncsend = 1; 106 break; 107 case 'y': 108 syncrecv = 1; 109 break; 110 } 111 if (syncsend || syncrecv) { 112 if ((ent = getservbyname("dhcpd-sync", "udp")) == NULL) 113 errx(1, "Can't find service \"dhcpd-sync\" in " 114 "/etc/services"); 115 sync_port = ntohs(ent->s_port); 116 } 117 118 udpaddr.s_addr = htonl(INADDR_BROADCAST); 119 120 optreset = optind = opterr = 1; 121 while ((ch = getopt(argc, argv, "A:C:L:c:dfl:nu::vY:y:")) != -1) 122 switch (ch) { 123 case 'A': 124 abandoned_tab = optarg; 125 break; 126 case 'C': 127 changedmac_tab = optarg; 128 break; 129 case 'L': 130 leased_tab = optarg; 131 break; 132 case 'c': 133 path_dhcpd_conf = optarg; 134 break; 135 case 'd': 136 /* FALLTHROUGH */ 137 case 'f': 138 debug = 1; 139 break; 140 case 'l': 141 path_dhcpd_db = optarg; 142 break; 143 case 'n': 144 debug = 1; 145 cftest = 1; 146 break; 147 case 'u': 148 udpsockmode = 1; 149 if (optarg != NULL) { 150 if (inet_aton(optarg, &udpaddr) != 1) 151 errx(1, "Cannot parse binding IP " 152 "address: %s", optarg); 153 } 154 break; 155 case 'v': 156 verbose = 1; 157 break; 158 case 'Y': 159 if (sync_addhost(optarg, sync_port) != 0) 160 sync_iface = optarg; 161 syncsend = 1; 162 break; 163 case 'y': 164 sync_baddr = optarg; 165 syncrecv = 1; 166 break; 167 default: 168 usage(); 169 } 170 171 argc -= optind; 172 argv += optind; 173 174 while (argc > 0) { 175 struct interface_info *tmp = calloc(1, sizeof(*tmp)); 176 if (!tmp) 177 fatalx("calloc"); 178 strlcpy(tmp->name, argv[0], sizeof(tmp->name)); 179 tmp->next = interfaces; 180 interfaces = tmp; 181 argc--; 182 argv++; 183 } 184 185 /* Default DHCP/BOOTP ports. */ 186 server_port = htons(SERVER_PORT); 187 client_port = htons(CLIENT_PORT); 188 189 tzset(); 190 191 time(&cur_time); 192 if (!readconf()) 193 fatalx("Configuration file errors encountered"); 194 195 if (cftest) 196 exit(0); 197 198 db_startup(); 199 if (!udpsockmode || argc > 0) 200 discover_interfaces(&rdomain); 201 202 if (rdomain != -1) 203 if (setrtable(rdomain) == -1) 204 fatal("setrtable"); 205 206 if (syncsend || syncrecv) { 207 syncfd = sync_init(sync_iface, sync_baddr, sync_port); 208 if (syncfd == -1) 209 err(1, "sync init"); 210 } 211 212 log_init(debug, LOG_DAEMON); 213 log_setverbose(verbose); 214 215 if (!debug) 216 daemon(0, 0); 217 218 if ((pw = getpwnam("_dhcp")) == NULL) 219 fatalx("user \"_dhcp\" not found"); 220 221 /* don't go near /dev/pf unless we actually intend to use it */ 222 if ((abandoned_tab != NULL) || 223 (changedmac_tab != NULL) || 224 (leased_tab != NULL)){ 225 if (pipe(pfpipe) == -1) 226 fatal("pipe"); 227 switch (pfproc_pid = fork()){ 228 case -1: 229 fatal("fork"); 230 /* NOTREACHED */ 231 exit(1); 232 case 0: 233 /* child process. start up table engine */ 234 close(pfpipe[1]); 235 pftable_handler(); 236 /* NOTREACHED */ 237 exit(1); 238 default: 239 close(pfpipe[0]); 240 gotpipe = 1; 241 break; 242 } 243 } 244 245 if (udpsockmode) 246 udpsock_startup(udpaddr); 247 248 icmp_startup(1, lease_pinged); 249 250 if (chroot(pw->pw_dir) == -1) 251 fatal("chroot %s", pw->pw_dir); 252 if (chdir("/") == -1) 253 fatal("chdir(\"/\")"); 254 if (setgroups(1, &pw->pw_gid) || 255 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 256 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 257 fatal("can't drop privileges"); 258 259 if (udpsockmode) { 260 if (pledge("stdio inet route sendfd", NULL) == -1) 261 err(1, "pledge"); 262 } else { 263 if (pledge("stdio inet sendfd", NULL) == -1) 264 err(1, "pledge"); 265 } 266 267 add_timeout(cur_time + 5, periodic_scan, NULL); 268 dispatch(); 269 270 /* not reached */ 271 exit(0); 272 } 273 274 __dead void 275 usage(void) 276 { 277 extern char *__progname; 278 279 fprintf(stderr, "usage: %s [-dfnv] [-A abandoned_ip_table]", 280 __progname); 281 fprintf(stderr, " [-C changed_ip_table]\n"); 282 fprintf(stderr, "\t[-c config-file] [-L leased_ip_table]"); 283 fprintf(stderr, " [-l lease-file] [-u[bind_address]]\n"); 284 fprintf(stderr, "\t[-Y synctarget] [-y synclisten] [if0 [... ifN]]\n"); 285 exit(1); 286 } 287 288 void 289 lease_pinged(struct iaddr from, u_int8_t *packet, int length) 290 { 291 struct lease *lp; 292 293 /* 294 * Don't try to look up a pinged lease if we aren't trying to 295 * ping one - otherwise somebody could easily make us churn by 296 * just forging repeated ICMP EchoReply packets for us to look 297 * up. 298 */ 299 if (!outstanding_pings) 300 return; 301 302 lp = find_lease_by_ip_addr(from); 303 304 if (!lp) { 305 log_info("unexpected ICMP Echo Reply from %s", piaddr(from)); 306 return; 307 } 308 309 if (!lp->state && !lp->releasing) { 310 log_warnx("ICMP Echo Reply for %s arrived late or is " 311 "spurious.", piaddr(from)); 312 return; 313 } 314 315 /* At this point it looks like we pinged a lease and got a 316 * response, which shouldn't have happened. 317 * if it did it's either one of two two cases: 318 * 1 - we pinged this lease before offering it and 319 * something answered, so we abandon it. 320 * 2 - we pinged this lease before releasing it 321 * and something answered, so we don't release it. 322 */ 323 if (lp->releasing) { 324 log_warnx("IP address %s answers a ping after sending a " 325 "release", piaddr(lp->ip_addr)); 326 log_warnx("Possible release spoof - Not releasing address %s", 327 piaddr(lp->ip_addr)); 328 lp->releasing = 0; 329 } else { 330 free_lease_state(lp->state, "lease_pinged"); 331 lp->state = NULL; 332 abandon_lease(lp, "pinged before offer"); 333 } 334 cancel_timeout(lease_ping_timeout, lp); 335 --outstanding_pings; 336 } 337 338 void 339 lease_ping_timeout(void *vlp) 340 { 341 struct lease *lp = vlp; 342 343 --outstanding_pings; 344 if (lp->releasing) { 345 lp->releasing = 0; 346 release_lease(lp); 347 } else 348 dhcp_reply(lp); 349 } 350 351 /* from memory.c - needed to be able to walk the lease table */ 352 extern struct subnet *subnets; 353 354 #define MINIMUM(a,b) (((a)<(b))?(a):(b)) 355 356 void 357 periodic_scan(void *p) 358 { 359 time_t x, y; 360 struct subnet *n; 361 struct group *g; 362 struct shared_network *s; 363 struct lease *l; 364 365 /* find the shortest lease this server gives out */ 366 x = MINIMUM(root_group.default_lease_time, root_group.max_lease_time); 367 for (n = subnets; n; n = n->next_subnet) 368 for (g = n->group; g; g = g->next) 369 x = MINIMUM(x, g->default_lease_time); 370 371 /* use half of the shortest lease as the scan interval */ 372 y = x / 2; 373 if (y < 1) 374 y = 1; 375 376 /* walk across all leases to find the exired ones */ 377 for (n = subnets; n; n = n->next_subnet) 378 for (g = n->group; g; g = g->next) 379 for (s = g->shared_network; s; s = s->next) 380 for (l = s->leases; l && l->ends; l = l->next) 381 if (cur_time >= l->ends) 382 if (l->ends > last_scan) 383 pfmsg('R', l); 384 385 last_scan = cur_time; 386 add_timeout(cur_time + y, periodic_scan, NULL); 387 } 388