xref: /openbsd-src/usr.sbin/dhcrelay/dhcrelay.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: dhcrelay.c,v 1.42 2016/09/15 16:16:03 jca Exp $ */
2 
3 /*
4  * Copyright (c) 2004 Henning Brauer <henning@cvs.openbsd.org>
5  * Copyright (c) 1997, 1998, 1999 The Internet Software Consortium.
6  * 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/ioctl.h>
44 #include <sys/socket.h>
45 
46 #include <arpa/inet.h>
47 
48 #include <net/if.h>
49 
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <netdb.h>
53 #include <paths.h>
54 #include <pwd.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <syslog.h>
59 #include <time.h>
60 #include <unistd.h>
61 
62 #include "dhcp.h"
63 #include "dhcpd.h"
64 
65 void	 usage(void);
66 int	 rdaemon(int);
67 void	 relay(struct interface_info *, struct dhcp_packet *, int,
68 	    unsigned int, struct iaddr, struct hardware *);
69 char	*print_hw_addr(int, int, unsigned char *);
70 void	 got_response(struct protocol *);
71 int	 get_rdomain(char *);
72 
73 ssize_t	 relay_agentinfo(struct interface_info *, struct dhcp_packet *,
74 	    size_t, struct in_addr *, struct in_addr *);
75 
76 time_t cur_time;
77 
78 int log_perror = 1;
79 
80 u_int16_t server_port;
81 u_int16_t client_port;
82 int log_priority;
83 struct interface_info *interfaces = NULL;
84 int server_fd;
85 int oflag;
86 
87 struct server_list {
88 	struct server_list *next;
89 	struct sockaddr_in to;
90 	int fd;
91 } *servers;
92 
93 int
94 main(int argc, char *argv[])
95 {
96 	int			 ch, devnull = -1, daemonize, opt, rdomain;
97 	extern char		*__progname;
98 	struct server_list	*sp = NULL;
99 	struct passwd		*pw;
100 	struct sockaddr_in	 laddr;
101 
102 	daemonize = 1;
103 
104 	/* Initially, log errors to stderr as well as to syslogd. */
105 	openlog(__progname, LOG_NDELAY, DHCPD_LOG_FACILITY);
106 	setlogmask(LOG_UPTO(LOG_INFO));
107 
108 	while ((ch = getopt(argc, argv, "adi:o")) != -1) {
109 		switch (ch) {
110 		case 'd':
111 			daemonize = 0;
112 			break;
113 		case 'i':
114 			if (interfaces != NULL)
115 				usage();
116 			if ((interfaces = calloc(1,
117 			    sizeof(struct interface_info))) == NULL)
118 				error("calloc");
119 			strlcpy(interfaces->name, optarg,
120 			    sizeof(interfaces->name));
121 			break;
122 		case 'o':
123 			/* add the relay agent information option */
124 			oflag++;
125 			break;
126 
127 		default:
128 			usage();
129 			/* not reached */
130 		}
131 	}
132 
133 	argc -= optind;
134 	argv += optind;
135 
136 	if (argc < 1)
137 		usage();
138 
139 	while (argc > 0) {
140 		struct hostent		*he;
141 		struct in_addr		 ia, *iap = NULL;
142 
143 		if (inet_aton(argv[0], &ia))
144 			iap = &ia;
145 		else {
146 			he = gethostbyname(argv[0]);
147 			if (!he)
148 				warning("%s: host unknown", argv[0]);
149 			else
150 				iap = ((struct in_addr *)he->h_addr_list[0]);
151 		}
152 		if (iap) {
153 			if ((sp = calloc(1, sizeof *sp)) == NULL)
154 				error("calloc");
155 			sp->next = servers;
156 			servers = sp;
157 			memcpy(&sp->to.sin_addr, iap, sizeof *iap);
158 		}
159 		argc--;
160 		argv++;
161 	}
162 
163 	if (daemonize) {
164 		devnull = open(_PATH_DEVNULL, O_RDWR, 0);
165 		if (devnull == -1)
166 			error("open(%s): %m", _PATH_DEVNULL);
167 	}
168 
169 	if (interfaces == NULL)
170 		error("no interface given");
171 
172 	/* Default DHCP/BOOTP ports. */
173 	server_port = htons(SERVER_PORT);
174 	client_port = htons(CLIENT_PORT);
175 
176 	/* We need at least one server. */
177 	if (!sp)
178 		usage();
179 
180 	discover_interfaces(interfaces);
181 
182 	rdomain = get_rdomain(interfaces->name);
183 
184 	/* Enable the relay agent option by default for enc0 */
185 	if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL)
186 		oflag++;
187 
188 	bzero(&laddr, sizeof laddr);
189 	laddr.sin_len = sizeof laddr;
190 	laddr.sin_family = AF_INET;
191 	laddr.sin_port = server_port;
192 	laddr.sin_addr.s_addr = interfaces->primary_address.s_addr;
193 	/* Set up the server sockaddrs. */
194 	for (sp = servers; sp; sp = sp->next) {
195 		sp->to.sin_port = server_port;
196 		sp->to.sin_family = AF_INET;
197 		sp->to.sin_len = sizeof sp->to;
198 		sp->fd = socket(AF_INET, SOCK_DGRAM, 0);
199 		if (sp->fd == -1)
200 			error("socket: %m");
201 		opt = 1;
202 		if (setsockopt(sp->fd, SOL_SOCKET, SO_REUSEPORT,
203 		    &opt, sizeof(opt)) == -1)
204 			error("setsockopt: %m");
205 		if (setsockopt(sp->fd, SOL_SOCKET, SO_RTABLE, &rdomain,
206 		    sizeof(rdomain)) == -1)
207 			error("setsockopt: %m");
208 		if (bind(sp->fd, (struct sockaddr *)&laddr, sizeof laddr) == -1)
209 			error("bind: %m");
210 		if (connect(sp->fd, (struct sockaddr *)&sp->to,
211 		    sizeof sp->to) == -1)
212 			error("connect: %m");
213 		add_protocol("server", sp->fd, got_response, sp);
214 	}
215 
216 	/* Socket used to forward packets to the DHCP client */
217 	if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
218 		laddr.sin_addr.s_addr = INADDR_ANY;
219 		server_fd = socket(AF_INET, SOCK_DGRAM, 0);
220 		if (server_fd == -1)
221 			error("socket: %m");
222 		opt = 1;
223 		if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT,
224 		    &opt, sizeof(opt)) == -1)
225 			error("setsockopt: %m");
226 		if (setsockopt(server_fd, SOL_SOCKET, SO_RTABLE, &rdomain,
227 		    sizeof(rdomain)) == -1)
228 			error("setsockopt: %m");
229 		if (bind(server_fd, (struct sockaddr *)&laddr,
230 		    sizeof(laddr)) == -1)
231 			error("bind: %m");
232 	}
233 
234 	tzset();
235 
236 	time(&cur_time);
237 	bootp_packet_handler = relay;
238 
239 	if ((pw = getpwnam("_dhcp")) == NULL)
240 		error("user \"_dhcp\" not found");
241 	if (chroot(_PATH_VAREMPTY) == -1)
242 		error("chroot: %m");
243 	if (chdir("/") == -1)
244 		error("chdir(\"/\"): %m");
245 	if (setgroups(1, &pw->pw_gid) ||
246 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
247 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
248 		error("can't drop privileges: %m");
249 
250 	if (daemonize) {
251 		if (rdaemon(devnull) == -1)
252 			error("rdaemon: %m");
253 		log_perror = 0;
254 	}
255 
256 	dispatch();
257 	/* not reached */
258 
259 	exit(0);
260 }
261 
262 void
263 relay(struct interface_info *ip, struct dhcp_packet *packet, int length,
264     unsigned int from_port, struct iaddr from, struct hardware *hfrom)
265 {
266 	struct server_list	*sp;
267 	struct sockaddr_in	 to;
268 	struct hardware		 hto;
269 
270 	if (packet->hlen > sizeof packet->chaddr) {
271 		note("Discarding packet with invalid hlen.");
272 		return;
273 	}
274 
275 	/* If it's a bootreply, forward it to the client. */
276 	if (packet->op == BOOTREPLY) {
277 		bzero(&to, sizeof(to));
278 		if (!(packet->flags & htons(BOOTP_BROADCAST))) {
279 			to.sin_addr = packet->yiaddr;
280 			to.sin_port = client_port;
281 		} else {
282 			to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
283 			to.sin_port = client_port;
284 		}
285 		to.sin_family = AF_INET;
286 		to.sin_len = sizeof to;
287 
288 		/* Set up the hardware destination address. */
289 		hto.hlen = packet->hlen;
290 		if (hto.hlen > sizeof hto.haddr)
291 			hto.hlen = sizeof hto.haddr;
292 		memcpy(hto.haddr, packet->chaddr, hto.hlen);
293 		hto.htype = packet->htype;
294 
295 		if ((length = relay_agentinfo(interfaces,
296 		    packet, length, NULL, &to.sin_addr)) == -1) {
297 			note("ignoring BOOTREPLY with invalid "
298 			    "relay agent information");
299 			return;
300 		}
301 
302 		/*
303 		 * VMware PXE "ROMs" confuse the DHCP gateway address
304 		 * with the IP gateway address. This is a problem if your
305 		 * DHCP relay is running on something that's not your
306 		 * network gateway.
307 		 *
308 		 * It is purely informational from the relay to the client
309 		 * so we can safely clear it.
310 		 */
311 		packet->giaddr.s_addr = 0x0;
312 
313 		if (send_packet(interfaces, packet, length,
314 		    interfaces->primary_address, &to, &hto) != -1)
315 			debug("forwarded BOOTREPLY for %s to %s",
316 			    print_hw_addr(packet->htype, packet->hlen,
317 			    packet->chaddr), inet_ntoa(to.sin_addr));
318 		return;
319 	}
320 
321 	if (ip == NULL) {
322 		note("ignoring non BOOTREPLY from server");
323 		return;
324 	}
325 
326 	/* If giaddr is set on a BOOTREQUEST, ignore it - it's already
327 	   been gatewayed. */
328 	if (packet->giaddr.s_addr) {
329 		note("ignoring BOOTREQUEST with giaddr of %s",
330 		    inet_ntoa(packet->giaddr));
331 		return;
332 	}
333 
334 	/* Set the giaddr so the server can figure out what net it's
335 	   from and so that we can later forward the response to the
336 	   correct net. */
337 	packet->giaddr = ip->primary_address;
338 
339 	if ((length = relay_agentinfo(ip, packet, length,
340 	    (struct in_addr *)from.iabuf, NULL)) == -1) {
341 		note("ignoring BOOTREQUEST with invalid "
342 		    "relay agent information");
343 		return;
344 	}
345 
346 	/* Otherwise, it's a BOOTREQUEST, so forward it to all the
347 	   servers. */
348 	for (sp = servers; sp; sp = sp->next) {
349 		if (send(sp->fd, packet, length, 0) != -1) {
350 			debug("forwarded BOOTREQUEST for %s to %s",
351 			    print_hw_addr(packet->htype, packet->hlen,
352 			    packet->chaddr), inet_ntoa(sp->to.sin_addr));
353 		}
354 	}
355 
356 }
357 
358 void
359 usage(void)
360 {
361 	extern char	*__progname;
362 
363 	fprintf(stderr, "usage: %s [-do] -i interface server1 [... serverN]\n",
364 	    __progname);
365 	exit(1);
366 }
367 
368 int
369 rdaemon(int devnull)
370 {
371 
372 	switch (fork()) {
373 	case -1:
374 		return (-1);
375 	case 0:
376 		break;
377 	default:
378 		_exit(0);
379 	}
380 
381 	if (setsid() == -1)
382 		return (-1);
383 
384 	(void)dup2(devnull, STDIN_FILENO);
385 	(void)dup2(devnull, STDOUT_FILENO);
386 	(void)dup2(devnull, STDERR_FILENO);
387 	if (devnull > 2)
388 		(void)close(devnull);
389 
390 	return (0);
391 }
392 
393 char *
394 print_hw_addr(int htype, int hlen, unsigned char *data)
395 {
396 	static char	 habuf[49];
397 	char		*s = habuf;
398 	int		 i, j, slen = sizeof(habuf);
399 
400 	if (htype == 0 || hlen == 0) {
401 bad:
402 		strlcpy(habuf, "<null>", sizeof habuf);
403 		return habuf;
404 	}
405 
406 	for (i = 0; i < hlen; i++) {
407 		j = snprintf(s, slen, "%02x", data[i]);
408 		if (j <= 0 || j >= slen)
409 			goto bad;
410 		j = strlen (s);
411 		s += j;
412 		slen -= (j + 1);
413 		*s++ = ':';
414 	}
415 	*--s = '\0';
416 	return habuf;
417 }
418 
419 void
420 got_response(struct protocol *l)
421 {
422 	ssize_t result;
423 	struct iaddr ifrom;
424 	union {
425 		/*
426 		 * Packet input buffer.  Must be as large as largest
427 		 * possible MTU.
428 		 */
429 		unsigned char packbuf[4095];
430 		struct dhcp_packet packet;
431 	} u;
432 	struct server_list *sp = l->local;
433 
434 	memset(&u, DHO_END, sizeof(u));
435 	if ((result = recv(l->fd, u.packbuf, sizeof(u), 0)) == -1 &&
436 	    errno != ECONNREFUSED) {
437 		/*
438 		 * Ignore ECONNREFUSED as too many dhcp servers send a bogus
439 		 * icmp unreach for every request.
440 		 */
441 		warning("recv failed for %s: %m",
442 		    inet_ntoa(sp->to.sin_addr));
443 		return;
444 	}
445 	if (result == -1 && errno == ECONNREFUSED)
446 		return;
447 
448 	if (result == 0)
449 		return;
450 
451 	if (result < BOOTP_MIN_LEN) {
452 		note("Discarding packet with invalid size.");
453 		return;
454 	}
455 
456 	if (bootp_packet_handler) {
457 		ifrom.len = 4;
458 		memcpy(ifrom.iabuf, &sp->to.sin_addr, ifrom.len);
459 
460 		(*bootp_packet_handler)(NULL, &u.packet, result,
461 		    sp->to.sin_port, ifrom, NULL);
462 	}
463 }
464 
465 ssize_t
466 relay_agentinfo(struct interface_info *info, struct dhcp_packet *packet,
467     size_t length, struct in_addr *from, struct in_addr *to)
468 {
469 	u_int8_t	*p;
470 	u_int		 i, j, railen;
471 	ssize_t		 optlen, maxlen, grow;
472 
473 	if (!oflag)
474 		return (length);
475 
476 	/* Buffer length vs. received packet length */
477 	maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1;
478 	optlen = length - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
479 	if (maxlen < 1 || optlen < 1)
480 		return (length);
481 
482 	if (memcmp(packet->options, DHCP_OPTIONS_COOKIE,
483 	    DHCP_OPTIONS_COOKIE_LEN) != 0)
484 		return (length);
485 	p = packet->options + DHCP_OPTIONS_COOKIE_LEN;
486 
487 	for (i = 0; i < (u_int)optlen && *p != DHO_END;) {
488 		if (*p == DHO_PAD)
489 			j = 1;
490 		else
491 			j = p[1] + 2;
492 
493 		if ((i + j) > (u_int)optlen) {
494 			warning("truncated dhcp options");
495 			break;
496 		}
497 
498 		/* Revert any other relay agent information */
499 		if (*p == DHO_RELAY_AGENT_INFORMATION) {
500 			if (to != NULL) {
501 				/* Check the relay agent information */
502 				railen = 8 + sizeof(struct in_addr);
503 				if (j >= railen &&
504 				    p[1] == (railen - 2) &&
505 				    p[2] == RAI_CIRCUIT_ID &&
506 				    p[3] == 2 &&
507 				    p[4] == (u_int8_t)(info->index << 8) &&
508 				    p[5] == (info->index & 0xff) &&
509 				    p[6] == RAI_REMOTE_ID &&
510 				    p[7] == sizeof(*to))
511 					memcpy(to, p + 8, sizeof(*to));
512 
513 				/* It should be the last option */
514 				memset(p, 0, j);
515 				*p = DHO_END;
516 			} else {
517 				/* Discard invalid option from a client */
518 				if (!packet->giaddr.s_addr)
519 					return (-1);
520 			}
521 			return (length);
522 		}
523 
524 		p += j;
525 		i += j;
526 
527 		if (from != NULL && (*p == DHO_END || (i >= optlen))) {
528 			j = 8 + sizeof(*from);
529 			if ((i + j) > (u_int)maxlen) {
530 				warning("skipping agent information");
531 				break;
532 			}
533 
534 			/* Append the relay agent information if it fits */
535 			p[0] = DHO_RELAY_AGENT_INFORMATION;
536 			p[1] = j - 2;
537 			p[2] = RAI_CIRCUIT_ID;
538 			p[3] = 2;
539 			p[4] = info->index << 8;
540 			p[5] = info->index & 0xff;
541 			p[6] = RAI_REMOTE_ID;
542 			p[7] = sizeof(*from);
543 			memcpy(p + 8, from, sizeof(*from));
544 
545 			/* Do we need to increase the packet length? */
546 			grow = j + 1 - (optlen - i);
547 			if (grow > 0)
548 				length += grow;
549 			p += j;
550 
551 			*p = DHO_END;
552 			break;
553 		}
554 	}
555 
556 	return (length);
557 }
558 
559 int
560 get_rdomain(char *name)
561 {
562 	int rv = 0, s;
563 	struct  ifreq ifr;
564 
565 	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
566 		error("get_rdomain socket: %m");
567 
568 	bzero(&ifr, sizeof(ifr));
569 	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
570 	if (ioctl(s, SIOCGIFRDOMAIN, (caddr_t)&ifr) != -1)
571 		rv = ifr.ifr_rdomainid;
572 
573 	close(s);
574 	return rv;
575 }
576