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