xref: /openbsd-src/usr.sbin/dhcpd/dhcpd.c (revision e13b122f5943afc27f987d29a2980051b6c01e83)
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