xref: /openbsd-src/usr.sbin/dhcpd/dhcpd.c (revision 2b0358df1d88d06ef4139321dd05bd5e05d91eaf)
1 /*	$OpenBSD: dhcpd.c,v 1.38 2008/05/29 19:02:47 deraadt 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 "dhcpd.h"
43 #include "sync.h"
44 
45 #include <err.h>
46 #include <pwd.h>
47 
48 void usage(void);
49 
50 time_t cur_time;
51 struct group root_group;
52 
53 u_int16_t server_port;
54 u_int16_t client_port;
55 
56 struct passwd *pw;
57 int log_priority;
58 int log_perror = 0;
59 int pfpipe[2];
60 int gotpipe = 0;
61 int syncrecv;
62 int syncsend;
63 pid_t pfproc_pid = -1;
64 char *path_dhcpd_conf = _PATH_DHCPD_CONF;
65 char *path_dhcpd_db = _PATH_DHCPD_DB;
66 char *abandoned_tab = NULL;
67 char *changedmac_tab = NULL;
68 char *leased_tab = NULL;
69 struct syslog_data sdata = SYSLOG_DATA_INIT;
70 
71 int
72 main(int argc, char *argv[])
73 {
74 	int ch, cftest = 0, daemonize = 1;
75 	extern char *__progname;
76 	char *sync_iface = NULL;
77 	char *sync_baddr = NULL;
78 	u_short sync_port = 0;
79 	struct servent *ent;
80 
81 	/* Initially, log errors to stderr as well as to syslogd. */
82 	openlog_r(__progname, LOG_PID | LOG_NDELAY, DHCPD_LOG_FACILITY, &sdata);
83 
84 	opterr = 0;
85 	while ((ch = getopt(argc, argv, "Y:y:")) != -1)
86 		switch (ch) {
87 		case 'Y':
88 			syncsend = 1;
89 			break;
90 		case 'y':
91 			syncrecv = 1;
92 			break;
93 		}
94 	if (syncsend || syncrecv) {
95 		if ((ent = getservbyname("dhcpd-sync", "udp")) == NULL)
96 			errx(1, "Can't find service \"dhcpd-sync\" in "
97 			    "/etc/services");
98 		sync_port = ntohs(ent->s_port);
99 	}
100 
101 	optreset = optind = opterr = 1;
102 	while ((ch = getopt(argc, argv, "A:C:L:c:dfl:nY:y:")) != -1)
103 		switch (ch) {
104 		case 'A':
105 			abandoned_tab = optarg;
106 			break;
107 		case 'C':
108 			changedmac_tab = optarg;
109 			break;
110 		case 'L':
111 			leased_tab = optarg;
112 			break;
113 		case 'c':
114 			path_dhcpd_conf = optarg;
115 			break;
116 		case 'd':
117 			daemonize = 0;
118 			log_perror = 1;
119 			break;
120 		case 'f':
121 			daemonize = 0;
122 			break;
123 		case 'l':
124 			path_dhcpd_db = optarg;
125 			break;
126 		case 'n':
127 			daemonize = 0;
128 			cftest = 1;
129 			log_perror = 1;
130 			break;
131 		case 'Y':
132 			if (sync_addhost(optarg, sync_port) != 0)
133 				sync_iface = optarg;
134 			syncsend = 1;
135 			break;
136 		case 'y':
137 			sync_baddr = optarg;
138 			syncrecv = 1;
139 			break;
140 		default:
141 			usage();
142 		}
143 
144 	argc -= optind;
145 	argv += optind;
146 
147 	while (argc > 0) {
148 		struct interface_info *tmp = calloc(1, sizeof(*tmp));
149 		if (!tmp)
150 			error("calloc");
151 		strlcpy(tmp->name, argv[0], sizeof(tmp->name));
152 		tmp->next = interfaces;
153 		interfaces = tmp;
154 		argc--;
155 		argv++;
156 	}
157 
158 	/* Default DHCP/BOOTP ports. */
159 	server_port = htons(SERVER_PORT);
160 	client_port = htons(CLIENT_PORT);
161 
162 	tzset();
163 
164 	time(&cur_time);
165 	if (!readconf())
166 		error("Configuration file errors encountered");
167 
168 	if (cftest)
169 		exit(0);
170 
171 	if (syncsend || syncrecv) {
172 		syncfd = sync_init(sync_iface, sync_baddr, sync_port);
173 		if (syncfd == -1)
174 			err(1, "sync init");
175 	}
176 
177 	db_startup();
178 	discover_interfaces();
179 	icmp_startup(1, lease_pinged);
180 
181 
182 	if ((pw = getpwnam("_dhcp")) == NULL)
183 		error("user \"_dhcp\" not found");
184 
185 	if (daemonize)
186 		daemon(0, 0);
187 
188 	/* don't go near /dev/pf unless we actually intend to use it */
189 	if ((abandoned_tab != NULL) ||
190 	    (changedmac_tab != NULL) ||
191 	    (leased_tab != NULL)){
192 		if (pipe(pfpipe) == -1)
193 			error("pipe (%m)");
194 		switch (pfproc_pid = fork()){
195 		case -1:
196 			error("fork (%m)");
197 			/* NOTREACHED */
198 			exit(1);
199 		case 0:
200 			/* child process. start up table engine */
201 			pftable_handler();
202 			/* NOTREACHED */
203 			exit(1);
204 		default:
205 			gotpipe = 1;
206 			break;
207 		}
208 	}
209 
210 	if (chroot(_PATH_VAREMPTY) == -1)
211 		error("chroot %s: %m", _PATH_VAREMPTY);
212 	if (chdir("/") == -1)
213 		error("chdir(\"/\"): %m");
214 	if (setgroups(1, &pw->pw_gid) ||
215 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
216 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
217 		error("can't drop privileges: %m");
218 
219 	bootp_packet_handler = do_packet;
220 	add_timeout(cur_time + 5, periodic_scan, NULL);
221 	dispatch();
222 
223 	/* not reached */
224 	exit(0);
225 }
226 
227 void
228 usage(void)
229 {
230 	extern char *__progname;
231 
232 	fprintf(stderr, "usage: %s [-dfn] [-A abandoned_ip_table]", __progname);
233 	fprintf(stderr, " [-C changed_ip_table]\n");
234 	fprintf(stderr, "\t[-c config-file] [-L leased_ip_table]");
235 	fprintf(stderr, " [-l lease-file] [-Y synctarget] [-y synclisten]\n");
236 	fprintf(stderr, "\t[if0 [... ifN]]\n");
237 	exit(1);
238 }
239 
240 void
241 lease_pinged(struct iaddr from, u_int8_t *packet, int length)
242 {
243 	struct lease *lp;
244 
245 	/*
246 	 * Don't try to look up a pinged lease if we aren't trying to
247 	 * ping one - otherwise somebody could easily make us churn by
248 	 * just forging repeated ICMP EchoReply packets for us to look
249 	 * up.
250 	 */
251 	if (!outstanding_pings)
252 		return;
253 
254 	lp = find_lease_by_ip_addr(from);
255 
256 	if (!lp) {
257 		note("unexpected ICMP Echo Reply from %s", piaddr(from));
258 		return;
259 	}
260 
261 	if (!lp->state && !lp->releasing) {
262 		warning("ICMP Echo Reply for %s arrived late or is spurious.",
263 		    piaddr(from));
264 		return;
265 	}
266 
267 	/* At this point it looks like we pinged a lease and got a
268 	 * response, which shouldn't have happened.
269 	 * if it did it's either one of two two cases:
270 	 * 1 - we pinged this lease before offering it and
271 	 *     something answered, so we abandon it.
272 	 * 2 - we pinged this lease before releasing it
273 	 *     and something answered, so we don't release it.
274 	 */
275 	if (lp->releasing) {
276 		warning("IP address %s answers a ping after sending a release",
277 		    piaddr(lp->ip_addr));
278 		warning("Possible release spoof - Not releasing address %s",
279 		    piaddr(lp->ip_addr));
280 		lp->releasing = 0;
281 	} else {
282 		free_lease_state(lp->state, "lease_pinged");
283 		lp->state = NULL;
284 		abandon_lease(lp, "pinged before offer");
285 	}
286 	cancel_timeout(lease_ping_timeout, lp);
287 	--outstanding_pings;
288 }
289 
290 void
291 lease_ping_timeout(void *vlp)
292 {
293 	struct lease	*lp = vlp;
294 
295 	--outstanding_pings;
296 	if (lp->releasing) {
297 		lp->releasing = 0;
298 		release_lease(lp);
299 	} else
300 		dhcp_reply(lp);
301 }
302 
303 /* from memory.c - needed to be able to walk the lease table */
304 extern struct subnet *subnets;
305 
306 void
307 periodic_scan(void *p)
308 {
309 	time_t x, y;
310 	struct subnet		*n;
311 	struct group		*g;
312 	struct shared_network	*s;
313 	struct lease		*l;
314 
315 	/* find the shortest lease this server gives out */
316 	x = MIN(root_group.default_lease_time, root_group.max_lease_time);
317 	for (n = subnets; n; n = n->next_subnet)
318 		for (g = n->group; g; g = g->next)
319 			x = MIN(x, g->default_lease_time);
320 
321 	/* use half of the shortest lease as the scan interval */
322 	y = x / 2;
323 	if (y < 1)
324 		y = 1;
325 
326 	/* walk across all leases to find the exired ones */
327 	for (n = subnets; n; n = n->next_subnet)
328 		for (g = n->group; g; g = g->next)
329 			for (s = g->shared_network; s; s = s->next)
330 				for (l = s->leases; l && l->ends; l = l->next)
331 					if (cur_time >= l->ends){
332 						release_lease(l);
333 						pfmsg('R', l);
334 					}
335 
336 	add_timeout(cur_time + y, periodic_scan, NULL);
337 }
338