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