1*e13b122fSflorian /* $OpenBSD: dhcpd.c,v 1.60 2024/08/21 09:19:55 florian Exp $ */ 248c73ebbShenning 3e853bc5dShenning /* 475c32ea9Shenning * Copyright (c) 2004 Henning Brauer <henning@cvs.openbsd.org> 5e853bc5dShenning * Copyright (c) 1995, 1996, 1997, 1998, 1999 6e853bc5dShenning * The Internet Software Consortium. All rights reserved. 7e853bc5dShenning * 8e853bc5dShenning * Redistribution and use in source and binary forms, with or without 9e853bc5dShenning * modification, are permitted provided that the following conditions 10e853bc5dShenning * are met: 11e853bc5dShenning * 12e853bc5dShenning * 1. Redistributions of source code must retain the above copyright 13e853bc5dShenning * notice, this list of conditions and the following disclaimer. 14e853bc5dShenning * 2. Redistributions in binary form must reproduce the above copyright 15e853bc5dShenning * notice, this list of conditions and the following disclaimer in the 16e853bc5dShenning * documentation and/or other materials provided with the distribution. 17e853bc5dShenning * 3. Neither the name of The Internet Software Consortium nor the names 18e853bc5dShenning * of its contributors may be used to endorse or promote products derived 19e853bc5dShenning * from this software without specific prior written permission. 20e853bc5dShenning * 21e853bc5dShenning * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND 22e853bc5dShenning * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 23e853bc5dShenning * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 24e853bc5dShenning * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25e853bc5dShenning * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR 26e853bc5dShenning * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27e853bc5dShenning * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28e853bc5dShenning * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 29e853bc5dShenning * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 30e853bc5dShenning * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31e853bc5dShenning * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 32e853bc5dShenning * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33e853bc5dShenning * SUCH DAMAGE. 34e853bc5dShenning * 35e853bc5dShenning * This software has been written for the Internet Software Consortium 36e853bc5dShenning * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie 37e853bc5dShenning * Enterprises. To learn more about the Internet Software Consortium, 38e853bc5dShenning * see ``http://www.vix.com/isc''. To learn more about Vixie 39e853bc5dShenning * Enterprises, see ``http://www.vix.com''. 40e853bc5dShenning */ 41e853bc5dShenning 42837cddffSkrw #include <sys/types.h> 43837cddffSkrw #include <sys/socket.h> 44837cddffSkrw 45837cddffSkrw #include <net/if.h> 46837cddffSkrw 47837cddffSkrw #include <arpa/inet.h> 48837cddffSkrw 49837cddffSkrw #include <err.h> 50837cddffSkrw #include <netdb.h> 51837cddffSkrw #include <pwd.h> 52837cddffSkrw #include <stdio.h> 53837cddffSkrw #include <stdlib.h> 54837cddffSkrw #include <string.h> 55837cddffSkrw #include <syslog.h> 56579e3f2dSguenther #include <time.h> 57837cddffSkrw #include <unistd.h> 58837cddffSkrw 59837cddffSkrw #include "dhcp.h" 60837cddffSkrw #include "tree.h" 61e853bc5dShenning #include "dhcpd.h" 62c525a185Skrw #include "log.h" 635f515bebSbeck #include "sync.h" 646cdc1343Sstevesk 65e853bc5dShenning 6646823010Skrw __dead void usage(void); 67e853bc5dShenning 681dcc068aSkrw time_t cur_time, last_scan; 69e853bc5dShenning struct group root_group; 70e853bc5dShenning 71390956b7Scanacar u_int16_t server_port; 72390956b7Scanacar u_int16_t client_port; 73e853bc5dShenning 746f4dfa88Sckuethe struct passwd *pw; 75e853bc5dShenning int log_priority; 766f4dfa88Sckuethe int pfpipe[2]; 776f4dfa88Sckuethe int gotpipe = 0; 785f515bebSbeck int syncrecv; 795f515bebSbeck int syncsend; 806f4dfa88Sckuethe pid_t pfproc_pid = -1; 81e853bc5dShenning char *path_dhcpd_conf = _PATH_DHCPD_CONF; 82e853bc5dShenning char *path_dhcpd_db = _PATH_DHCPD_DB; 836f4dfa88Sckuethe char *abandoned_tab = NULL; 846f4dfa88Sckuethe char *changedmac_tab = NULL; 852cadf9d6Sckuethe char *leased_tab = NULL; 86e853bc5dShenning 8775c32ea9Shenning int 8875c32ea9Shenning main(int argc, char *argv[]) 89e853bc5dShenning { 901e4d45b4Smvs int ch, cftest = 0, rdomain = -1, udpsockmode = 0; 911e4d45b4Smvs int debug = 0, verbose = 0; 925f515bebSbeck char *sync_iface = NULL; 935f515bebSbeck char *sync_baddr = NULL; 94b377a340Sderaadt u_short sync_port = 0; 955f515bebSbeck struct servent *ent; 9684d8c049Syasuoka struct in_addr udpaddr; 97e853bc5dShenning 98c525a185Skrw log_init(1, LOG_DAEMON); /* log to stderr until daemonized */ 99c525a185Skrw log_setverbose(1); 100e853bc5dShenning 1015382abf8Smillert opterr = 0; 1021e4d45b4Smvs while ((ch = getopt(argc, argv, "A:C:L:c:dfl:nu::vY:y:")) != -1) 1035382abf8Smillert switch (ch) { 1045382abf8Smillert case 'Y': 1055382abf8Smillert syncsend = 1; 1065382abf8Smillert break; 1075382abf8Smillert case 'y': 1085382abf8Smillert syncrecv = 1; 1095382abf8Smillert break; 1105382abf8Smillert } 1115382abf8Smillert if (syncsend || syncrecv) { 1125f515bebSbeck if ((ent = getservbyname("dhcpd-sync", "udp")) == NULL) 1135382abf8Smillert errx(1, "Can't find service \"dhcpd-sync\" in " 1145382abf8Smillert "/etc/services"); 1155f515bebSbeck sync_port = ntohs(ent->s_port); 1165382abf8Smillert } 1175f515bebSbeck 11884d8c049Syasuoka udpaddr.s_addr = htonl(INADDR_BROADCAST); 11984d8c049Syasuoka 1205382abf8Smillert optreset = optind = opterr = 1; 1211e4d45b4Smvs while ((ch = getopt(argc, argv, "A:C:L:c:dfl:nu::vY:y:")) != -1) 12217d8acc5Shenning switch (ch) { 1236f4dfa88Sckuethe case 'A': 1246f4dfa88Sckuethe abandoned_tab = optarg; 1256f4dfa88Sckuethe break; 1266f4dfa88Sckuethe case 'C': 1276f4dfa88Sckuethe changedmac_tab = optarg; 1286f4dfa88Sckuethe break; 1292cadf9d6Sckuethe case 'L': 1302cadf9d6Sckuethe leased_tab = optarg; 1312cadf9d6Sckuethe break; 13217d8acc5Shenning case 'c': 13317d8acc5Shenning path_dhcpd_conf = optarg; 13417d8acc5Shenning break; 13517d8acc5Shenning case 'd': 1361e4d45b4Smvs /* FALLTHROUGH */ 13717d8acc5Shenning case 'f': 1381e4d45b4Smvs debug = 1; 13917d8acc5Shenning break; 14017d8acc5Shenning case 'l': 14117d8acc5Shenning path_dhcpd_db = optarg; 14217d8acc5Shenning break; 143f90d8862Scanacar case 'n': 1441e4d45b4Smvs debug = 1; 145e853bc5dShenning cftest = 1; 14617d8acc5Shenning break; 14784d8c049Syasuoka case 'u': 14884d8c049Syasuoka udpsockmode = 1; 14984d8c049Syasuoka if (optarg != NULL) { 150*e13b122fSflorian if (inet_pton(AF_INET, optarg, &udpaddr) != 1) 15184d8c049Syasuoka errx(1, "Cannot parse binding IP " 15284d8c049Syasuoka "address: %s", optarg); 15384d8c049Syasuoka } 15484d8c049Syasuoka break; 1551e4d45b4Smvs case 'v': 1561e4d45b4Smvs verbose = 1; 1571e4d45b4Smvs break; 1585f515bebSbeck case 'Y': 1595f515bebSbeck if (sync_addhost(optarg, sync_port) != 0) 1605f515bebSbeck sync_iface = optarg; 1615382abf8Smillert syncsend = 1; 1625f515bebSbeck break; 1635f515bebSbeck case 'y': 1645f515bebSbeck sync_baddr = optarg; 1655382abf8Smillert syncrecv = 1; 1665f515bebSbeck break; 16717d8acc5Shenning default: 16875c32ea9Shenning usage(); 16917d8acc5Shenning } 17017d8acc5Shenning 17117d8acc5Shenning argc -= optind; 17217d8acc5Shenning argv += optind; 17317d8acc5Shenning 17417d8acc5Shenning while (argc > 0) { 17517d8acc5Shenning struct interface_info *tmp = calloc(1, sizeof(*tmp)); 176e853bc5dShenning if (!tmp) 177c525a185Skrw fatalx("calloc"); 17817d8acc5Shenning strlcpy(tmp->name, argv[0], sizeof(tmp->name)); 179e853bc5dShenning tmp->next = interfaces; 180e853bc5dShenning interfaces = tmp; 18117d8acc5Shenning argc--; 18217d8acc5Shenning argv++; 183e853bc5dShenning } 184e853bc5dShenning 185390956b7Scanacar /* Default DHCP/BOOTP ports. */ 186390956b7Scanacar server_port = htons(SERVER_PORT); 187390956b7Scanacar client_port = htons(CLIENT_PORT); 188e853bc5dShenning 18986b5d282Shenning tzset(); 19086b5d282Shenning 191fbc5e94dShenning time(&cur_time); 192e853bc5dShenning if (!readconf()) 193c525a185Skrw fatalx("Configuration file errors encountered"); 194e853bc5dShenning 195e853bc5dShenning if (cftest) 196e853bc5dShenning exit(0); 197e853bc5dShenning 198daa83f4cSclaudio db_startup(); 19984d8c049Syasuoka if (!udpsockmode || argc > 0) 200daa83f4cSclaudio discover_interfaces(&rdomain); 201daa83f4cSclaudio 202daa83f4cSclaudio if (rdomain != -1) 2038bb39f08Sguenther if (setrtable(rdomain) == -1) 2040438cf0aSkrw fatal("setrtable"); 205daa83f4cSclaudio 2065f515bebSbeck if (syncsend || syncrecv) { 2075f515bebSbeck syncfd = sync_init(sync_iface, sync_baddr, sync_port); 2085f515bebSbeck if (syncfd == -1) 2095f515bebSbeck err(1, "sync init"); 2105f515bebSbeck } 2115f515bebSbeck 2121e4d45b4Smvs log_init(debug, LOG_DAEMON); 2131e4d45b4Smvs log_setverbose(verbose); 214e853bc5dShenning 2151e4d45b4Smvs if (!debug) 2161e4d45b4Smvs daemon(0, 0); 217c525a185Skrw 21846823010Skrw if ((pw = getpwnam("_dhcp")) == NULL) 219c525a185Skrw fatalx("user \"_dhcp\" not found"); 22046823010Skrw 2216f4dfa88Sckuethe /* don't go near /dev/pf unless we actually intend to use it */ 2222cadf9d6Sckuethe if ((abandoned_tab != NULL) || 2232cadf9d6Sckuethe (changedmac_tab != NULL) || 2242cadf9d6Sckuethe (leased_tab != NULL)){ 2256f4dfa88Sckuethe if (pipe(pfpipe) == -1) 2260438cf0aSkrw fatal("pipe"); 2276f4dfa88Sckuethe switch (pfproc_pid = fork()){ 2286f4dfa88Sckuethe case -1: 2290438cf0aSkrw fatal("fork"); 2306f4dfa88Sckuethe /* NOTREACHED */ 2316f4dfa88Sckuethe exit(1); 2326f4dfa88Sckuethe case 0: 2336f4dfa88Sckuethe /* child process. start up table engine */ 2348950268cSkrw close(pfpipe[1]); 2356f4dfa88Sckuethe pftable_handler(); 2366f4dfa88Sckuethe /* NOTREACHED */ 2376f4dfa88Sckuethe exit(1); 2386f4dfa88Sckuethe default: 2398950268cSkrw close(pfpipe[0]); 2406f4dfa88Sckuethe gotpipe = 1; 2416f4dfa88Sckuethe break; 2426f4dfa88Sckuethe } 2436f4dfa88Sckuethe } 2446f4dfa88Sckuethe 2450ac20dc2Smestre if (udpsockmode) 24646823010Skrw udpsock_startup(udpaddr); 24746823010Skrw 24846823010Skrw icmp_startup(1, lease_pinged); 24946823010Skrw 2508552a089Skrw if (chroot(pw->pw_dir) == -1) 2518552a089Skrw fatal("chroot %s", pw->pw_dir); 2528d5b94c2Shenning if (chdir("/") == -1) 2530438cf0aSkrw fatal("chdir(\"/\")"); 2548d5b94c2Shenning if (setgroups(1, &pw->pw_gid) || 2553e7173e3Sdjm setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 2563e7173e3Sdjm setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 2570438cf0aSkrw fatal("can't drop privileges"); 2588d5b94c2Shenning 25946823010Skrw if (udpsockmode) { 26046823010Skrw if (pledge("stdio inet route sendfd", NULL) == -1) 26146823010Skrw err(1, "pledge"); 26246823010Skrw } else { 26346823010Skrw if (pledge("stdio inet sendfd", NULL) == -1) 26446823010Skrw err(1, "pledge"); 26546823010Skrw } 26646823010Skrw 2672cadf9d6Sckuethe add_timeout(cur_time + 5, periodic_scan, NULL); 268e853bc5dShenning dispatch(); 269e853bc5dShenning 270c71fd70fShenning /* not reached */ 271c71fd70fShenning exit(0); 272e853bc5dShenning } 273e853bc5dShenning 27446823010Skrw __dead void 27575c32ea9Shenning usage(void) 276e853bc5dShenning { 27775c32ea9Shenning extern char *__progname; 278e853bc5dShenning 279fbb678beSjmc fprintf(stderr, "usage: %s [-dfnv] [-A abandoned_ip_table]", 28035318e8fSkrw __progname); 2819e82e05fSckuethe fprintf(stderr, " [-C changed_ip_table]\n"); 282e4d11a38Sjmc fprintf(stderr, "\t[-c config-file] [-L leased_ip_table]"); 28384d8c049Syasuoka fprintf(stderr, " [-l lease-file] [-u[bind_address]]\n"); 28484d8c049Syasuoka fprintf(stderr, "\t[-Y synctarget] [-y synclisten] [if0 [... ifN]]\n"); 28575c32ea9Shenning exit(1); 286e853bc5dShenning } 287e853bc5dShenning 288c71fd70fShenning void 289c71fd70fShenning lease_pinged(struct iaddr from, u_int8_t *packet, int length) 290e853bc5dShenning { 291e853bc5dShenning struct lease *lp; 292e853bc5dShenning 293c71fd70fShenning /* 294c71fd70fShenning * Don't try to look up a pinged lease if we aren't trying to 295c71fd70fShenning * ping one - otherwise somebody could easily make us churn by 296c71fd70fShenning * just forging repeated ICMP EchoReply packets for us to look 297c71fd70fShenning * up. 298c71fd70fShenning */ 299e853bc5dShenning if (!outstanding_pings) 300e853bc5dShenning return; 301e853bc5dShenning 302e853bc5dShenning lp = find_lease_by_ip_addr(from); 303e853bc5dShenning 304e853bc5dShenning if (!lp) { 305c525a185Skrw log_info("unexpected ICMP Echo Reply from %s", piaddr(from)); 306e853bc5dShenning return; 307e853bc5dShenning } 308e853bc5dShenning 309e853bc5dShenning if (!lp->state && !lp->releasing) { 31035318e8fSkrw log_warnx("ICMP Echo Reply for %s arrived late or is " 31135318e8fSkrw "spurious.", piaddr(from)); 312e853bc5dShenning return; 313e853bc5dShenning } 314e853bc5dShenning 315e853bc5dShenning /* At this point it looks like we pinged a lease and got a 316e853bc5dShenning * response, which shouldn't have happened. 317e853bc5dShenning * if it did it's either one of two two cases: 318e853bc5dShenning * 1 - we pinged this lease before offering it and 319e853bc5dShenning * something answered, so we abandon it. 320c71fd70fShenning * 2 - we pinged this lease before releasing it 321e853bc5dShenning * and something answered, so we don't release it. 322e853bc5dShenning */ 323e853bc5dShenning if (lp->releasing) { 32435318e8fSkrw log_warnx("IP address %s answers a ping after sending a " 32535318e8fSkrw "release", piaddr(lp->ip_addr)); 326c525a185Skrw log_warnx("Possible release spoof - Not releasing address %s", 327e853bc5dShenning piaddr(lp->ip_addr)); 328e853bc5dShenning lp->releasing = 0; 329c71fd70fShenning } else { 330e853bc5dShenning free_lease_state(lp->state, "lease_pinged"); 331c71fd70fShenning lp->state = NULL; 332e853bc5dShenning abandon_lease(lp, "pinged before offer"); 333e853bc5dShenning } 334e853bc5dShenning cancel_timeout(lease_ping_timeout, lp); 335e853bc5dShenning --outstanding_pings; 336e853bc5dShenning } 337e853bc5dShenning 338c71fd70fShenning void 339c71fd70fShenning lease_ping_timeout(void *vlp) 340e853bc5dShenning { 341e853bc5dShenning struct lease *lp = vlp; 342e853bc5dShenning 343e853bc5dShenning --outstanding_pings; 344e853bc5dShenning if (lp->releasing) { 345e853bc5dShenning lp->releasing = 0; 346e853bc5dShenning release_lease(lp); 347c71fd70fShenning } else 348e853bc5dShenning dhcp_reply(lp); 349e853bc5dShenning } 3502cadf9d6Sckuethe 3512cadf9d6Sckuethe /* from memory.c - needed to be able to walk the lease table */ 3522cadf9d6Sckuethe extern struct subnet *subnets; 3532cadf9d6Sckuethe 354b9fc9a72Sderaadt #define MINIMUM(a,b) (((a)<(b))?(a):(b)) 355b9fc9a72Sderaadt 3562cadf9d6Sckuethe void 3572cadf9d6Sckuethe periodic_scan(void *p) 3582cadf9d6Sckuethe { 3592cadf9d6Sckuethe time_t x, y; 3602cadf9d6Sckuethe struct subnet *n; 3612cadf9d6Sckuethe struct group *g; 3622cadf9d6Sckuethe struct shared_network *s; 3632cadf9d6Sckuethe struct lease *l; 3642cadf9d6Sckuethe 3652cadf9d6Sckuethe /* find the shortest lease this server gives out */ 366b9fc9a72Sderaadt x = MINIMUM(root_group.default_lease_time, root_group.max_lease_time); 3672cadf9d6Sckuethe for (n = subnets; n; n = n->next_subnet) 3682cadf9d6Sckuethe for (g = n->group; g; g = g->next) 369b9fc9a72Sderaadt x = MINIMUM(x, g->default_lease_time); 3702cadf9d6Sckuethe 3712cadf9d6Sckuethe /* use half of the shortest lease as the scan interval */ 3722cadf9d6Sckuethe y = x / 2; 3732cadf9d6Sckuethe if (y < 1) 3742cadf9d6Sckuethe y = 1; 3752cadf9d6Sckuethe 3762cadf9d6Sckuethe /* walk across all leases to find the exired ones */ 3772cadf9d6Sckuethe for (n = subnets; n; n = n->next_subnet) 3782cadf9d6Sckuethe for (g = n->group; g; g = g->next) 3792cadf9d6Sckuethe for (s = g->shared_network; s; s = s->next) 3802cadf9d6Sckuethe for (l = s->leases; l && l->ends; l = l->next) 3811dcc068aSkrw if (cur_time >= l->ends) 3821dcc068aSkrw if (l->ends > last_scan) 3832cadf9d6Sckuethe pfmsg('R', l); 3842cadf9d6Sckuethe 3851dcc068aSkrw last_scan = cur_time; 3862cadf9d6Sckuethe add_timeout(cur_time + y, periodic_scan, NULL); 3872cadf9d6Sckuethe } 388