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
68time_t cur_time, last_scan;
69struct group root_group;
70
71u_int16_t server_port;
72u_int16_t client_port;
73
74struct passwd *pw;
75int log_priority;
76int pfpipe[2];
77int gotpipe = 0;
78int syncrecv;
79int syncsend;
80pid_t pfproc_pid = -1;
81char *path_dhcpd_conf = _PATH_DHCPD_CONF;
82char *path_dhcpd_db = _PATH_DHCPD_DB;
83char *abandoned_tab = NULL;
84char *changedmac_tab = NULL;
85char *leased_tab = NULL;
86
87int
88main(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
275usage(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
288void
289lease_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
338void
339lease_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 */
352extern struct subnet *subnets;
353
354#define MINIMUM(a,b) (((a)<(b))?(a):(b))
355
356void
357periodic_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