xref: /openbsd/usr.sbin/dhcpd/dhcpd.c (revision e13b122f)
1 /*	$OpenBSD: dhcpd.c,v 1.60 2024/08/21 09:19:55 florian 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
main(int argc,char * argv[])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_pton(AF_INET, 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
usage(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
lease_pinged(struct iaddr from,u_int8_t * packet,int length)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
lease_ping_timeout(void * vlp)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
periodic_scan(void * p)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