/* $OpenBSD: dhcpd.c,v 1.59 2023/10/06 05:31:54 jmc Exp $ */ /* * Copyright (c) 2004 Henning Brauer * Copyright (c) 1995, 1996, 1997, 1998, 1999 * The Internet Software Consortium. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of The Internet Software Consortium nor the names * of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This software has been written for the Internet Software Consortium * by Ted Lemon in cooperation with Vixie * Enterprises. To learn more about the Internet Software Consortium, * see ``http://www.vix.com/isc''. To learn more about Vixie * Enterprises, see ``http://www.vix.com''. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dhcp.h" #include "tree.h" #include "dhcpd.h" #include "log.h" #include "sync.h" __dead void usage(void); time_t cur_time, last_scan; struct group root_group; u_int16_t server_port; u_int16_t client_port; struct passwd *pw; int log_priority; int pfpipe[2]; int gotpipe = 0; int syncrecv; int syncsend; pid_t pfproc_pid = -1; char *path_dhcpd_conf = _PATH_DHCPD_CONF; char *path_dhcpd_db = _PATH_DHCPD_DB; char *abandoned_tab = NULL; char *changedmac_tab = NULL; char *leased_tab = NULL; int main(int argc, char *argv[]) { int ch, cftest = 0, rdomain = -1, udpsockmode = 0; int debug = 0, verbose = 0; char *sync_iface = NULL; char *sync_baddr = NULL; u_short sync_port = 0; struct servent *ent; struct in_addr udpaddr; log_init(1, LOG_DAEMON); /* log to stderr until daemonized */ log_setverbose(1); opterr = 0; while ((ch = getopt(argc, argv, "A:C:L:c:dfl:nu::vY:y:")) != -1) switch (ch) { case 'Y': syncsend = 1; break; case 'y': syncrecv = 1; break; } if (syncsend || syncrecv) { if ((ent = getservbyname("dhcpd-sync", "udp")) == NULL) errx(1, "Can't find service \"dhcpd-sync\" in " "/etc/services"); sync_port = ntohs(ent->s_port); } udpaddr.s_addr = htonl(INADDR_BROADCAST); optreset = optind = opterr = 1; while ((ch = getopt(argc, argv, "A:C:L:c:dfl:nu::vY:y:")) != -1) switch (ch) { case 'A': abandoned_tab = optarg; break; case 'C': changedmac_tab = optarg; break; case 'L': leased_tab = optarg; break; case 'c': path_dhcpd_conf = optarg; break; case 'd': /* FALLTHROUGH */ case 'f': debug = 1; break; case 'l': path_dhcpd_db = optarg; break; case 'n': debug = 1; cftest = 1; break; case 'u': udpsockmode = 1; if (optarg != NULL) { if (inet_aton(optarg, &udpaddr) != 1) errx(1, "Cannot parse binding IP " "address: %s", optarg); } break; case 'v': verbose = 1; break; case 'Y': if (sync_addhost(optarg, sync_port) != 0) sync_iface = optarg; syncsend = 1; break; case 'y': sync_baddr = optarg; syncrecv = 1; break; default: usage(); } argc -= optind; argv += optind; while (argc > 0) { struct interface_info *tmp = calloc(1, sizeof(*tmp)); if (!tmp) fatalx("calloc"); strlcpy(tmp->name, argv[0], sizeof(tmp->name)); tmp->next = interfaces; interfaces = tmp; argc--; argv++; } /* Default DHCP/BOOTP ports. */ server_port = htons(SERVER_PORT); client_port = htons(CLIENT_PORT); tzset(); time(&cur_time); if (!readconf()) fatalx("Configuration file errors encountered"); if (cftest) exit(0); db_startup(); if (!udpsockmode || argc > 0) discover_interfaces(&rdomain); if (rdomain != -1) if (setrtable(rdomain) == -1) fatal("setrtable"); if (syncsend || syncrecv) { syncfd = sync_init(sync_iface, sync_baddr, sync_port); if (syncfd == -1) err(1, "sync init"); } log_init(debug, LOG_DAEMON); log_setverbose(verbose); if (!debug) daemon(0, 0); if ((pw = getpwnam("_dhcp")) == NULL) fatalx("user \"_dhcp\" not found"); /* don't go near /dev/pf unless we actually intend to use it */ if ((abandoned_tab != NULL) || (changedmac_tab != NULL) || (leased_tab != NULL)){ if (pipe(pfpipe) == -1) fatal("pipe"); switch (pfproc_pid = fork()){ case -1: fatal("fork"); /* NOTREACHED */ exit(1); case 0: /* child process. start up table engine */ close(pfpipe[1]); pftable_handler(); /* NOTREACHED */ exit(1); default: close(pfpipe[0]); gotpipe = 1; break; } } if (udpsockmode) udpsock_startup(udpaddr); icmp_startup(1, lease_pinged); if (chroot(pw->pw_dir) == -1) fatal("chroot %s", pw->pw_dir); if (chdir("/") == -1) fatal("chdir(\"/\")"); if (setgroups(1, &pw->pw_gid) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("can't drop privileges"); if (udpsockmode) { if (pledge("stdio inet route sendfd", NULL) == -1) err(1, "pledge"); } else { if (pledge("stdio inet sendfd", NULL) == -1) err(1, "pledge"); } add_timeout(cur_time + 5, periodic_scan, NULL); dispatch(); /* not reached */ exit(0); } __dead void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-dfnv] [-A abandoned_ip_table]", __progname); fprintf(stderr, " [-C changed_ip_table]\n"); fprintf(stderr, "\t[-c config-file] [-L leased_ip_table]"); fprintf(stderr, " [-l lease-file] [-u[bind_address]]\n"); fprintf(stderr, "\t[-Y synctarget] [-y synclisten] [if0 [... ifN]]\n"); exit(1); } void lease_pinged(struct iaddr from, u_int8_t *packet, int length) { struct lease *lp; /* * Don't try to look up a pinged lease if we aren't trying to * ping one - otherwise somebody could easily make us churn by * just forging repeated ICMP EchoReply packets for us to look * up. */ if (!outstanding_pings) return; lp = find_lease_by_ip_addr(from); if (!lp) { log_info("unexpected ICMP Echo Reply from %s", piaddr(from)); return; } if (!lp->state && !lp->releasing) { log_warnx("ICMP Echo Reply for %s arrived late or is " "spurious.", piaddr(from)); return; } /* At this point it looks like we pinged a lease and got a * response, which shouldn't have happened. * if it did it's either one of two two cases: * 1 - we pinged this lease before offering it and * something answered, so we abandon it. * 2 - we pinged this lease before releasing it * and something answered, so we don't release it. */ if (lp->releasing) { log_warnx("IP address %s answers a ping after sending a " "release", piaddr(lp->ip_addr)); log_warnx("Possible release spoof - Not releasing address %s", piaddr(lp->ip_addr)); lp->releasing = 0; } else { free_lease_state(lp->state, "lease_pinged"); lp->state = NULL; abandon_lease(lp, "pinged before offer"); } cancel_timeout(lease_ping_timeout, lp); --outstanding_pings; } void lease_ping_timeout(void *vlp) { struct lease *lp = vlp; --outstanding_pings; if (lp->releasing) { lp->releasing = 0; release_lease(lp); } else dhcp_reply(lp); } /* from memory.c - needed to be able to walk the lease table */ extern struct subnet *subnets; #define MINIMUM(a,b) (((a)<(b))?(a):(b)) void periodic_scan(void *p) { time_t x, y; struct subnet *n; struct group *g; struct shared_network *s; struct lease *l; /* find the shortest lease this server gives out */ x = MINIMUM(root_group.default_lease_time, root_group.max_lease_time); for (n = subnets; n; n = n->next_subnet) for (g = n->group; g; g = g->next) x = MINIMUM(x, g->default_lease_time); /* use half of the shortest lease as the scan interval */ y = x / 2; if (y < 1) y = 1; /* walk across all leases to find the exired ones */ for (n = subnets; n; n = n->next_subnet) for (g = n->group; g; g = g->next) for (s = g->shared_network; s; s = s->next) for (l = s->leases; l && l->ends; l = l->next) if (cur_time >= l->ends) if (l->ends > last_scan) pfmsg('R', l); last_scan = cur_time; add_timeout(cur_time + y, periodic_scan, NULL); }