xref: /openbsd-src/usr.sbin/dhcpd/dhcpd.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /*	$OpenBSD: dhcpd.c,v 1.41 2010/12/15 14:34:17 claudio 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;
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, "A:C:L:c:dfl:nY: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 	db_startup();
172 	discover_interfaces(&rdomain);
173 
174 	if (rdomain != -1)
175 		if (setrtable(rdomain) == -1)
176 			error("setrtable (%m)");
177 
178 	icmp_startup(1, lease_pinged);
179 
180 	if (syncsend || syncrecv) {
181 		syncfd = sync_init(sync_iface, sync_baddr, sync_port);
182 		if (syncfd == -1)
183 			err(1, "sync init");
184 	}
185 
186 	if ((pw = getpwnam("_dhcp")) == NULL)
187 		error("user \"_dhcp\" not found");
188 
189 	if (daemonize)
190 		daemon(0, 0);
191 
192 	/* don't go near /dev/pf unless we actually intend to use it */
193 	if ((abandoned_tab != NULL) ||
194 	    (changedmac_tab != NULL) ||
195 	    (leased_tab != NULL)){
196 		if (pipe(pfpipe) == -1)
197 			error("pipe (%m)");
198 		switch (pfproc_pid = fork()){
199 		case -1:
200 			error("fork (%m)");
201 			/* NOTREACHED */
202 			exit(1);
203 		case 0:
204 			/* child process. start up table engine */
205 			pftable_handler();
206 			/* NOTREACHED */
207 			exit(1);
208 		default:
209 			gotpipe = 1;
210 			break;
211 		}
212 	}
213 
214 	if (chroot(_PATH_VAREMPTY) == -1)
215 		error("chroot %s: %m", _PATH_VAREMPTY);
216 	if (chdir("/") == -1)
217 		error("chdir(\"/\"): %m");
218 	if (setgroups(1, &pw->pw_gid) ||
219 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
220 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
221 		error("can't drop privileges: %m");
222 
223 	bootp_packet_handler = do_packet;
224 	add_timeout(cur_time + 5, periodic_scan, NULL);
225 	dispatch();
226 
227 	/* not reached */
228 	exit(0);
229 }
230 
231 void
232 usage(void)
233 {
234 	extern char *__progname;
235 
236 	fprintf(stderr, "usage: %s [-dfn] [-A abandoned_ip_table]", __progname);
237 	fprintf(stderr, " [-C changed_ip_table]\n");
238 	fprintf(stderr, "\t[-c config-file] [-L leased_ip_table]");
239 	fprintf(stderr, " [-l lease-file] [-Y synctarget] [-y synclisten]\n");
240 	fprintf(stderr, "\t[if0 [... ifN]]\n");
241 	exit(1);
242 }
243 
244 void
245 lease_pinged(struct iaddr from, u_int8_t *packet, int length)
246 {
247 	struct lease *lp;
248 
249 	/*
250 	 * Don't try to look up a pinged lease if we aren't trying to
251 	 * ping one - otherwise somebody could easily make us churn by
252 	 * just forging repeated ICMP EchoReply packets for us to look
253 	 * up.
254 	 */
255 	if (!outstanding_pings)
256 		return;
257 
258 	lp = find_lease_by_ip_addr(from);
259 
260 	if (!lp) {
261 		note("unexpected ICMP Echo Reply from %s", piaddr(from));
262 		return;
263 	}
264 
265 	if (!lp->state && !lp->releasing) {
266 		warning("ICMP Echo Reply for %s arrived late or is spurious.",
267 		    piaddr(from));
268 		return;
269 	}
270 
271 	/* At this point it looks like we pinged a lease and got a
272 	 * response, which shouldn't have happened.
273 	 * if it did it's either one of two two cases:
274 	 * 1 - we pinged this lease before offering it and
275 	 *     something answered, so we abandon it.
276 	 * 2 - we pinged this lease before releasing it
277 	 *     and something answered, so we don't release it.
278 	 */
279 	if (lp->releasing) {
280 		warning("IP address %s answers a ping after sending a release",
281 		    piaddr(lp->ip_addr));
282 		warning("Possible release spoof - Not releasing address %s",
283 		    piaddr(lp->ip_addr));
284 		lp->releasing = 0;
285 	} else {
286 		free_lease_state(lp->state, "lease_pinged");
287 		lp->state = NULL;
288 		abandon_lease(lp, "pinged before offer");
289 	}
290 	cancel_timeout(lease_ping_timeout, lp);
291 	--outstanding_pings;
292 }
293 
294 void
295 lease_ping_timeout(void *vlp)
296 {
297 	struct lease	*lp = vlp;
298 
299 	--outstanding_pings;
300 	if (lp->releasing) {
301 		lp->releasing = 0;
302 		release_lease(lp);
303 	} else
304 		dhcp_reply(lp);
305 }
306 
307 /* from memory.c - needed to be able to walk the lease table */
308 extern struct subnet *subnets;
309 
310 void
311 periodic_scan(void *p)
312 {
313 	time_t x, y;
314 	struct subnet		*n;
315 	struct group		*g;
316 	struct shared_network	*s;
317 	struct lease		*l;
318 
319 	/* find the shortest lease this server gives out */
320 	x = MIN(root_group.default_lease_time, root_group.max_lease_time);
321 	for (n = subnets; n; n = n->next_subnet)
322 		for (g = n->group; g; g = g->next)
323 			x = MIN(x, g->default_lease_time);
324 
325 	/* use half of the shortest lease as the scan interval */
326 	y = x / 2;
327 	if (y < 1)
328 		y = 1;
329 
330 	/* walk across all leases to find the exired ones */
331 	for (n = subnets; n; n = n->next_subnet)
332 		for (g = n->group; g; g = g->next)
333 			for (s = g->shared_network; s; s = s->next)
334 				for (l = s->leases; l && l->ends; l = l->next)
335 					if (cur_time >= l->ends){
336 						release_lease(l);
337 						pfmsg('R', l);
338 					}
339 
340 	add_timeout(cur_time + y, periodic_scan, NULL);
341 }
342