xref: /openbsd/usr.sbin/dhcrelay/dhcrelay.c (revision ac6360cb)
1 /*	$OpenBSD: dhcrelay.c,v 1.67 2024/08/21 10:35:12 florian 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 #include "log.h"
65 
66 void	 usage(void);
67 int	 rdaemon(int);
68 void	 relay(struct interface_info *, struct dhcp_packet *, int,
69 	    struct packet_ctx *);
70 void	 l2relay(struct interface_info *, struct dhcp_packet *, int,
71 	    struct packet_ctx *);
72 char	*print_hw_addr(int, int, unsigned char *);
73 void	 got_response(struct protocol *);
74 int	 get_rdomain(char *);
75 
76 void	 relay_agentinfo(struct packet_ctx *, struct interface_info *, int);
77 
78 int	 relay_agentinfo_cmp(struct packet_ctx *pc, uint8_t *, int);
79 ssize_t	 relay_agentinfo_append(struct packet_ctx *, struct dhcp_packet *,
80 	    size_t);
81 ssize_t	 relay_agentinfo_remove(struct packet_ctx *, struct dhcp_packet *,
82 	    size_t);
83 
84 time_t cur_time;
85 
86 struct interface_info *interfaces = NULL;
87 struct server_list *servers;
88 struct iflist intflist;
89 int server_fd;
90 int oflag;
91 
92 enum dhcp_relay_mode	 drm = DRM_UNKNOWN;
93 const char		*rai_circuit = NULL;
94 const char		*rai_remote = NULL;
95 int			 rai_replace = 0;
96 
97 int
main(int argc,char * argv[])98 main(int argc, char *argv[])
99 {
100 	int			 ch, devnull = -1, daemonize, opt, rdomain;
101 	struct server_list	*sp = NULL;
102 	struct passwd		*pw;
103 	struct sockaddr_in	 laddr;
104 	int			 optslen;
105 
106 	daemonize = 1;
107 
108 	log_init(1, LOG_DAEMON);	/* log to stderr until daemonized */
109 
110 	setup_iflist();
111 
112 	while ((ch = getopt(argc, argv, "aC:di:oR:r")) != -1) {
113 		switch (ch) {
114 		case 'C':
115 			rai_circuit = optarg;
116 			break;
117 		case 'd':
118 			daemonize = 0;
119 			break;
120 		case 'i':
121 			if (interfaces != NULL)
122 				usage();
123 
124 			interfaces = iflist_getbyname(optarg);
125 			if (interfaces == NULL)
126 				fatalx("interface '%s' not found", optarg);
127 			break;
128 		case 'o':
129 			/* add the relay agent information option */
130 			oflag++;
131 			break;
132 		case 'R':
133 			rai_remote = optarg;
134 			break;
135 		case 'r':
136 			rai_replace = 1;
137 			break;
138 
139 		default:
140 			usage();
141 			/* not reached */
142 		}
143 	}
144 
145 	argc -= optind;
146 	argv += optind;
147 
148 	if (argc < 1)
149 		usage();
150 
151 	if (rai_remote != NULL && rai_circuit == NULL)
152 		fatalx("you must specify a circuit-id with a remote-id");
153 
154 	/* Validate that we have space for all suboptions. */
155 	if (rai_circuit != NULL) {
156 		optslen = 2 + strlen(rai_circuit);
157 		if (rai_remote != NULL)
158 			optslen += 2 + strlen(rai_remote);
159 
160 		if (optslen > DHCP_OPTION_MAXLEN)
161 			fatalx("relay agent information is too long");
162 	}
163 
164 	while (argc > 0) {
165 		struct addrinfo hints, *res;
166 		struct in_addr		 ia, *iap = NULL;
167 
168 		if ((sp = calloc(1, sizeof(*sp))) == NULL)
169 			fatalx("calloc");
170 
171 		memset(&hints, 0, sizeof(hints));
172 		hints.ai_family = AF_INET;
173 
174 		if ((sp->intf = register_interface(argv[0], got_one,
175 		    1)) != NULL) {
176 			if (drm == DRM_LAYER3)
177 				fatalx("don't mix interfaces with hosts");
178 
179 			if (sp->intf->hw_address.htype == HTYPE_IPSEC_TUNNEL)
180 				fatalx("can't use IPsec with layer 2");
181 
182 			sp->next = servers;
183 			servers = sp;
184 
185 			drm = DRM_LAYER2;
186 			argc--;
187 			argv++;
188 			continue;
189 		}
190 
191 		if (getaddrinfo(argv[0], NULL, &hints, &res) != 0)
192 			log_warnx("%s: host unknown", argv[0]);
193 		else {
194 			ia = ((struct sockaddr_in *)res->ai_addr)->sin_addr;
195 			iap = &ia;
196 			freeaddrinfo(res);
197 		}
198 
199 		if (iap) {
200 			if (drm == DRM_LAYER2)
201 				fatalx("don't mix interfaces with hosts");
202 
203 			drm = DRM_LAYER3;
204 			sp->next = servers;
205 			servers = sp;
206 			memcpy(&ss2sin(&sp->to)->sin_addr, iap, sizeof(*iap));
207 		} else
208 			free(sp);
209 
210 		argc--;
211 		argv++;
212 	}
213 
214 	if (daemonize) {
215 		devnull = open(_PATH_DEVNULL, O_RDWR);
216 		if (devnull == -1)
217 			fatal("open(%s)", _PATH_DEVNULL);
218 	}
219 
220 	if (interfaces == NULL ||
221 	    register_interface(interfaces->name, got_one, 0) == NULL)
222 		fatalx("no interface given");
223 
224 	/* We need an address for running layer 3 mode. */
225 	if (drm == DRM_LAYER3 &&
226 	    (interfaces->hw_address.htype != HTYPE_IPSEC_TUNNEL &&
227 	    interfaces->primary_address.s_addr == 0))
228 		fatalx("interface '%s' does not have an address",
229 		    interfaces->name);
230 
231 	/* We need at least one server. */
232 	if (!sp)
233 		usage();
234 
235 	rdomain = get_rdomain(interfaces->name);
236 
237 	/* Enable the relay agent option by default for enc0 */
238 	if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL)
239 		oflag++;
240 
241 	bzero(&laddr, sizeof laddr);
242 	laddr.sin_len = sizeof laddr;
243 	laddr.sin_family = AF_INET;
244 	laddr.sin_port = htons(SERVER_PORT);
245 	laddr.sin_addr.s_addr = interfaces->primary_address.s_addr;
246 	/* Set up the server sockaddrs. */
247 	for (sp = servers; sp; sp = sp->next) {
248 		if (sp->intf != NULL)
249 			break;
250 
251 		ss2sin(&sp->to)->sin_port = htons(SERVER_PORT);
252 		ss2sin(&sp->to)->sin_family = AF_INET;
253 		ss2sin(&sp->to)->sin_len = sizeof(struct sockaddr_in);
254 		sp->fd = socket(AF_INET, SOCK_DGRAM, 0);
255 		if (sp->fd == -1)
256 			fatal("socket");
257 		opt = 1;
258 		if (setsockopt(sp->fd, SOL_SOCKET, SO_REUSEPORT,
259 		    &opt, sizeof(opt)) == -1)
260 			fatal("setsockopt");
261 		if (setsockopt(sp->fd, SOL_SOCKET, SO_RTABLE, &rdomain,
262 		    sizeof(rdomain)) == -1)
263 			fatal("setsockopt");
264 		if (bind(sp->fd, (struct sockaddr *)&laddr, sizeof laddr) ==
265 		    -1)
266 			fatal("bind");
267 		if (connect(sp->fd, (struct sockaddr *)&sp->to,
268 		    sp->to.ss_len) == -1)
269 			fatal("connect");
270 		add_protocol("server", sp->fd, got_response, sp);
271 	}
272 
273 	/* Socket used to forward packets to the DHCP client */
274 	if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
275 		laddr.sin_addr.s_addr = INADDR_ANY;
276 		server_fd = socket(AF_INET, SOCK_DGRAM, 0);
277 		if (server_fd == -1)
278 			fatal("socket");
279 		opt = 1;
280 		if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT,
281 		    &opt, sizeof(opt)) == -1)
282 			fatal("setsockopt");
283 		if (setsockopt(server_fd, SOL_SOCKET, SO_RTABLE, &rdomain,
284 		    sizeof(rdomain)) == -1)
285 			fatal("setsockopt");
286 		if (bind(server_fd, (struct sockaddr *)&laddr,
287 		    sizeof(laddr)) == -1)
288 			fatal("bind");
289 	}
290 
291 	tzset();
292 
293 	time(&cur_time);
294 	if (drm == DRM_LAYER3)
295 		bootp_packet_handler = relay;
296 	else
297 		bootp_packet_handler = l2relay;
298 
299 	if ((pw = getpwnam("_dhcp")) == NULL)
300 		fatalx("user \"_dhcp\" not found");
301 	if (chroot(pw->pw_dir) == -1)
302 		fatal("chroot");
303 	if (chdir("/") == -1)
304 		fatal("chdir(\"/\")");
305 	if (setgroups(1, &pw->pw_gid) ||
306 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
307 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
308 		fatal("can't drop privileges");
309 
310 	if (daemonize) {
311 		if (rdaemon(devnull) == -1)
312 			fatal("rdaemon");
313 
314 		log_init(0, LOG_DAEMON);	/* stop logging to stderr */
315 	}
316 
317 	if (pledge("stdio route", NULL) == -1)
318 		fatalx("pledge");
319 
320 	dispatch();
321 	/* not reached */
322 
323 	exit(0);
324 }
325 
326 void
relay(struct interface_info * ip,struct dhcp_packet * packet,int length,struct packet_ctx * pc)327 relay(struct interface_info *ip, struct dhcp_packet *packet, int length,
328     struct packet_ctx *pc)
329 {
330 	struct server_list	*sp;
331 	struct sockaddr_in	 to;
332 
333 	if (packet->hlen > sizeof packet->chaddr) {
334 		log_info("Discarding packet with invalid hlen.");
335 		return;
336 	}
337 
338 	/* If it's a bootreply, forward it to the client. */
339 	if (packet->op == BOOTREPLY) {
340 		/* Filter packet that were not meant for us. */
341 		if (packet->giaddr.s_addr !=
342 		    interfaces->primary_address.s_addr)
343 			return;
344 
345 		bzero(&to, sizeof(to));
346 		if (!(packet->flags & htons(BOOTP_BROADCAST))) {
347 			to.sin_addr = packet->yiaddr;
348 			to.sin_port = htons(CLIENT_PORT);
349 		} else {
350 			to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
351 			to.sin_port = htons(CLIENT_PORT);
352 		}
353 		to.sin_family = AF_INET;
354 		to.sin_len = sizeof to;
355 		*ss2sin(&pc->pc_dst) = to;
356 
357 		/*
358 		 * Set up the hardware destination address.  If it's a reply
359 		 * with the BROADCAST flag set, we should send an L2 broad-
360 		 * cast as well.
361 		 */
362 		if (!(packet->flags & htons(BOOTP_BROADCAST))) {
363 			pc->pc_hlen = packet->hlen;
364 			if (pc->pc_hlen > CHADDR_SIZE)
365 				pc->pc_hlen = CHADDR_SIZE;
366 			memcpy(pc->pc_dmac, packet->chaddr, pc->pc_hlen);
367 			pc->pc_htype = packet->htype;
368 		} else {
369 			memset(pc->pc_dmac, 0xff, sizeof(pc->pc_dmac));
370 		}
371 
372 		relay_agentinfo(pc, interfaces, packet->op);
373 		if ((length = relay_agentinfo_remove(pc, packet,
374 		    length)) == -1) {
375 			log_info("ignoring BOOTREPLY with invalid "
376 			    "relay agent information");
377 			return;
378 		}
379 
380 		/*
381 		 * VMware PXE "ROMs" confuse the DHCP gateway address
382 		 * with the IP gateway address. This is a problem if your
383 		 * DHCP relay is running on something that's not your
384 		 * network gateway.
385 		 *
386 		 * It is purely informational from the relay to the client
387 		 * so we can safely clear it.
388 		 */
389 		packet->giaddr.s_addr = 0x0;
390 
391 		ss2sin(&pc->pc_src)->sin_addr = interfaces->primary_address;
392 		if (send_packet(interfaces, packet, length, pc) != -1)
393 			log_debug("forwarded BOOTREPLY for %s to %s",
394 			    print_hw_addr(packet->htype, packet->hlen,
395 			    packet->chaddr), inet_ntoa(to.sin_addr));
396 		return;
397 	}
398 
399 	if (ip == NULL) {
400 		log_info("ignoring non BOOTREPLY from server");
401 		return;
402 	}
403 
404 	if (packet->hops > 16) {
405 		log_info("ignoring BOOTREQUEST with hop count of %d",
406 		    packet->hops);
407 		return;
408 	}
409 	packet->hops++;
410 
411 	/*
412 	 * Set the giaddr so the server can figure out what net it's
413 	 * from and so that we can later forward the response to the
414 	 * correct net.  The RFC specifies that we have to keep the
415 	 * initial giaddr (in case we relay over multiple hops).
416 	 */
417 	if (!packet->giaddr.s_addr)
418 		packet->giaddr = ip->primary_address;
419 
420 	relay_agentinfo(pc, interfaces, packet->op);
421 	if ((length = relay_agentinfo_append(pc, packet, length)) == -1) {
422 		log_info("ignoring BOOTREQUEST with invalid "
423 		    "relay agent information");
424 		return;
425 	}
426 
427 	/* Otherwise, it's a BOOTREQUEST, so forward it to all the
428 	   servers. */
429 	for (sp = servers; sp; sp = sp->next) {
430 		if (send(sp->fd, packet, length, 0) != -1) {
431 			log_debug("forwarded BOOTREQUEST for %s to %s",
432 			    print_hw_addr(packet->htype, packet->hlen,
433 			    packet->chaddr),
434 			    inet_ntoa(ss2sin(&sp->to)->sin_addr));
435 		}
436 	}
437 
438 }
439 
440 void
usage(void)441 usage(void)
442 {
443 	extern char	*__progname;
444 
445 	fprintf(stderr, "usage: %s [-dor] [-C circuit-id] [-R remote-id] "
446 	    "-i interface\n\tdestination ...\n",
447 	    __progname);
448 	exit(1);
449 }
450 
451 int
rdaemon(int devnull)452 rdaemon(int devnull)
453 {
454 	if (devnull == -1) {
455 		errno = EBADF;
456 		return (-1);
457 	}
458 	if (fcntl(devnull, F_GETFL) == -1)
459 		return (-1);
460 
461 	switch (fork()) {
462 	case -1:
463 		return (-1);
464 	case 0:
465 		break;
466 	default:
467 		_exit(0);
468 	}
469 
470 	if (setsid() == -1)
471 		return (-1);
472 
473 	(void)dup2(devnull, STDIN_FILENO);
474 	(void)dup2(devnull, STDOUT_FILENO);
475 	(void)dup2(devnull, STDERR_FILENO);
476 	if (devnull > 2)
477 		(void)close(devnull);
478 
479 	return (0);
480 }
481 
482 char *
print_hw_addr(int htype,int hlen,unsigned char * data)483 print_hw_addr(int htype, int hlen, unsigned char *data)
484 {
485 	static char	 habuf[49];
486 	char		*s = habuf;
487 	int		 i, j, slen = sizeof(habuf);
488 
489 	if (htype == 0 || hlen == 0) {
490 bad:
491 		strlcpy(habuf, "<null>", sizeof habuf);
492 		return habuf;
493 	}
494 
495 	for (i = 0; i < hlen; i++) {
496 		j = snprintf(s, slen, "%02x", data[i]);
497 		if (j <= 0 || j >= slen)
498 			goto bad;
499 		j = strlen (s);
500 		s += j;
501 		slen -= (j + 1);
502 		*s++ = ':';
503 	}
504 	*--s = '\0';
505 	return habuf;
506 }
507 
508 void
got_response(struct protocol * l)509 got_response(struct protocol *l)
510 {
511 	struct packet_ctx pc;
512 	ssize_t result;
513 	union {
514 		/*
515 		 * Packet input buffer.  Must be as large as largest
516 		 * possible MTU.
517 		 */
518 		unsigned char packbuf[4095];
519 		struct dhcp_packet packet;
520 	} u;
521 	struct server_list *sp = l->local;
522 
523 	memset(&u, DHO_END, sizeof(u));
524 	if ((result = recv(l->fd, u.packbuf, sizeof(u), 0)) == -1 &&
525 	    errno != ECONNREFUSED) {
526 		/*
527 		 * Ignore ECONNREFUSED as too many dhcp servers send a bogus
528 		 * icmp unreach for every request.
529 		 */
530 		log_warn("recv failed for %s",
531 		    inet_ntoa(ss2sin(&sp->to)->sin_addr));
532 		return;
533 	}
534 	if (result == -1 && errno == ECONNREFUSED)
535 		return;
536 
537 	if (result == 0)
538 		return;
539 
540 	if (result < BOOTP_MIN_LEN) {
541 		log_info("Discarding packet with invalid size.");
542 		return;
543 	}
544 
545 	memset(&pc, 0, sizeof(pc));
546 	pc.pc_src.ss_family = AF_INET;
547 	pc.pc_src.ss_len = sizeof(struct sockaddr_in);
548 	memcpy(&ss2sin(&pc.pc_src)->sin_addr, &ss2sin(&sp->to)->sin_addr,
549 	    sizeof(ss2sin(&pc.pc_src)->sin_addr));
550 	ss2sin(&pc.pc_src)->sin_port = htons(SERVER_PORT);
551 
552 	pc.pc_dst.ss_family = AF_INET;
553 	pc.pc_dst.ss_len = sizeof(struct sockaddr_in);
554 	ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT);
555 
556 	if (bootp_packet_handler)
557 		(*bootp_packet_handler)(NULL, &u.packet, result, &pc);
558 }
559 
560 void
relay_agentinfo(struct packet_ctx * pc,struct interface_info * intf,int bootop)561 relay_agentinfo(struct packet_ctx *pc, struct interface_info *intf,
562     int bootop)
563 {
564 	static u_int8_t		 buf[8];
565 	struct sockaddr_in	*sin;
566 
567 	if (oflag == 0)
568 		return;
569 
570 	if (rai_remote != NULL) {
571 		pc->pc_remote = rai_remote;
572 		pc->pc_remotelen = strlen(rai_remote);
573 	} else
574 		pc->pc_remotelen = 0;
575 
576 	if (rai_circuit == NULL) {
577 		buf[0] = (uint8_t)(intf->index << 8);
578 		buf[1] = intf->index & 0xff;
579 		pc->pc_circuit = buf;
580 		pc->pc_circuitlen = 2;
581 
582 		if (rai_remote == NULL) {
583 			if (bootop == BOOTREPLY)
584 				sin = ss2sin(&pc->pc_dst);
585 			else
586 				sin = ss2sin(&pc->pc_src);
587 
588 			pc->pc_remote =
589 			    (uint8_t *)&sin->sin_addr;
590 			pc->pc_remotelen =
591 			    sizeof(sin->sin_addr);
592 		}
593 	} else {
594 		pc->pc_circuit = rai_circuit;
595 		pc->pc_circuitlen = strlen(rai_circuit);
596 	}
597 }
598 
599 int
relay_agentinfo_cmp(struct packet_ctx * pc,uint8_t * p,int plen)600 relay_agentinfo_cmp(struct packet_ctx *pc, uint8_t *p, int plen)
601 {
602 	int		 len;
603 	char		 buf[256];
604 
605 	if (oflag == 0)
606 		return (-1);
607 
608 	len = *(p + 1);
609 	if (len > plen)
610 		return (-1);
611 
612 	switch (*p) {
613 	case RAI_CIRCUIT_ID:
614 		if (pc->pc_circuit == NULL)
615 			return (-1);
616 		if (pc->pc_circuitlen != len)
617 			return (-1);
618 
619 		memcpy(buf, p + DHCP_OPTION_HDR_LEN, len);
620 		return (memcmp(pc->pc_circuit, buf, len));
621 
622 	case RAI_REMOTE_ID:
623 		if (pc->pc_remote == NULL)
624 			return (-1);
625 		if (pc->pc_remotelen != len)
626 			return (-1);
627 
628 		memcpy(buf, p + DHCP_OPTION_HDR_LEN, len);
629 		return (memcmp(pc->pc_remote, buf, len));
630 
631 	default:
632 		/* Unmatched type */
633 		log_info("unmatched relay info %d", *p);
634 		return (0);
635 	}
636 }
637 
638 ssize_t
relay_agentinfo_append(struct packet_ctx * pc,struct dhcp_packet * dp,size_t dplen)639 relay_agentinfo_append(struct packet_ctx *pc, struct dhcp_packet *dp,
640     size_t dplen)
641 {
642 	uint8_t		*p, *startp;
643 	ssize_t		 newtotal = dplen;
644 	int		 opttotal, optlen, i, hasinfo = 0;
645 	int		 maxlen, neededlen;
646 
647 	/* Only append when enabled. */
648 	if (oflag == 0)
649 		return (dplen);
650 
651 	startp = (uint8_t *)dp;
652 	p = (uint8_t *)&dp->options;
653 	if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) {
654 		log_info("invalid dhcp options cookie");
655 		return (-1);
656 	}
657 
658 	p += DHCP_OPTIONS_COOKIE_LEN;
659 	opttotal = dplen - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
660 	maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1;
661 	if (maxlen < 1 || opttotal < 1)
662 		return (dplen);
663 
664 	for (i = 0; i < opttotal && *p != DHO_END;) {
665 		if (*p == DHO_PAD)
666 			optlen = 1;
667 		else
668 			optlen = p[1] + DHCP_OPTION_HDR_LEN;
669 
670 		if ((i + optlen) > opttotal) {
671 			log_info("truncated dhcp options");
672 			return (-1);
673 		}
674 
675 		if (*p == DHO_RELAY_AGENT_INFORMATION) {
676 			if (rai_replace) {
677 				memmove(p, p + optlen, opttotal - i);
678 				opttotal -= optlen;
679 				optlen = 0;
680 			} else
681 				hasinfo = 1;
682 		}
683 
684 		p += optlen;
685 		i += optlen;
686 
687 		/* We reached the end, append the relay agent info. */
688 		if (i < opttotal && *p == DHO_END) {
689 			/* We already have the Relay Agent Info, skip it. */
690 			if (hasinfo)
691 				continue;
692 
693 			/* Calculate needed length to append new data. */
694 			neededlen = newtotal + DHCP_OPTION_HDR_LEN;
695 			if (pc->pc_circuitlen > 0)
696 				neededlen += DHCP_OPTION_HDR_LEN +
697 				    pc->pc_circuitlen;
698 			if (pc->pc_remotelen > 0)
699 				neededlen += DHCP_OPTION_HDR_LEN +
700 				    pc->pc_remotelen;
701 
702 			/* Save one byte for DHO_END. */
703 			neededlen += 1;
704 
705 			/* Check if we have space for the new options. */
706 			if (neededlen > maxlen) {
707 				log_warnx("no space for relay agent info");
708 				return (newtotal);
709 			}
710 
711 			/* New option header: 2 bytes. */
712 			newtotal += DHCP_OPTION_HDR_LEN;
713 
714 			*p++ = DHO_RELAY_AGENT_INFORMATION;
715 			*p = 0;
716 			if (pc->pc_circuitlen > 0) {
717 				newtotal += DHCP_OPTION_HDR_LEN +
718 				    pc->pc_circuitlen;
719 				*p = (*p) + DHCP_OPTION_HDR_LEN +
720 				    pc->pc_circuitlen;
721 			}
722 
723 			if (pc->pc_remotelen > 0) {
724 				newtotal += DHCP_OPTION_HDR_LEN +
725 				    pc->pc_remotelen;
726 				*p = (*p) + DHCP_OPTION_HDR_LEN +
727 				    pc->pc_remotelen;
728 			}
729 
730 			p++;
731 
732 			/* Sub-option circuit-id header plus value. */
733 			if (pc->pc_circuitlen > 0) {
734 				*p++ = RAI_CIRCUIT_ID;
735 				*p++ = pc->pc_circuitlen;
736 				memcpy(p, pc->pc_circuit, pc->pc_circuitlen);
737 
738 				p += pc->pc_circuitlen;
739 			}
740 
741 			/* Sub-option remote-id header plus value. */
742 			if (pc->pc_remotelen > 0) {
743 				*p++ = RAI_REMOTE_ID;
744 				*p++ = pc->pc_remotelen;
745 				memcpy(p, pc->pc_remote, pc->pc_remotelen);
746 
747 				p += pc->pc_remotelen;
748 			}
749 
750 			*p = DHO_END;
751 		}
752 	}
753 
754 	/* Zero the padding so we don't leak anything. */
755 	p++;
756 	if (p < (startp + maxlen))
757 		memset(p, 0, (startp + maxlen) - p);
758 
759 	return (newtotal);
760 }
761 
762 ssize_t
relay_agentinfo_remove(struct packet_ctx * pc,struct dhcp_packet * dp,size_t dplen)763 relay_agentinfo_remove(struct packet_ctx *pc, struct dhcp_packet *dp,
764     size_t dplen)
765 {
766 	uint8_t		*p, *np, *startp, *endp;
767 	int		 opttotal, optleft;
768 	int		 suboptlen, optlen, i;
769 	int		 maxlen, remaining, matched = 0;
770 
771 	startp = (uint8_t *)dp;
772 	p = (uint8_t *)&dp->options;
773 	if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) {
774 		log_info("invalid dhcp options cookie");
775 		return (-1);
776 	}
777 
778 	maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1;
779 	opttotal = dplen - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
780 	optleft = opttotal;
781 
782 	p += DHCP_OPTIONS_COOKIE_LEN;
783 	endp = p + opttotal;
784 
785 	for (i = 0; i < opttotal && *p != DHO_END;) {
786 		if (*p == DHO_PAD)
787 			optlen = 1;
788 		else
789 			optlen = p[1] + DHCP_OPTION_HDR_LEN;
790 
791 		if ((i + optlen) > opttotal) {
792 			log_info("truncated dhcp options");
793 			return (-1);
794 		}
795 
796 		if (*p == DHO_RELAY_AGENT_INFORMATION) {
797 			/* Fast case: there is no next option. */
798 			np = p + optlen;
799 			if (*np == DHO_END) {
800 				*p = *np;
801 				endp = p + 1;
802 				/* Zero the padding so we don't leak data. */
803 				if (endp < (startp + maxlen))
804 					memset(endp, 0,
805 					    (startp + maxlen) - endp);
806 
807 				return (dplen);
808 			}
809 
810 			remaining = optlen;
811 			while (remaining > 0) {
812 				suboptlen = *(p + 1);
813 				remaining -= DHCP_OPTION_HDR_LEN + suboptlen;
814 
815 				matched = 1;
816 				if (relay_agentinfo_cmp(pc, p, suboptlen) == 0)
817 					continue;
818 
819 				matched = 0;
820 				break;
821 			}
822 			/* It is not ours Relay Agent Info, don't remove it. */
823 			if (matched == 0)
824 				break;
825 
826 			/* Move the other options on top of this one. */
827 			optleft -= optlen;
828 			endp -= optlen;
829 
830 			/* Replace the old agent relay info. */
831 			memmove(p, dp, optleft);
832 
833 			endp++;
834 			/* Zero the padding so we don't leak data. */
835 			if (endp < (startp + maxlen))
836 				memset(endp, 0,
837 				    (startp + maxlen) - endp);
838 
839 			return (endp - startp);
840 		}
841 
842 		p += optlen;
843 		i += optlen;
844 		optleft -= optlen;
845 	}
846 
847 	return (endp - startp);
848 }
849 
850 int
get_rdomain(char * name)851 get_rdomain(char *name)
852 {
853 	int rv = 0, s;
854 	struct  ifreq ifr;
855 
856 	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
857 		fatal("get_rdomain socket");
858 
859 	bzero(&ifr, sizeof(ifr));
860 	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
861 	if (ioctl(s, SIOCGIFRDOMAIN, (caddr_t)&ifr) != -1)
862 		rv = ifr.ifr_rdomainid;
863 
864 	close(s);
865 	return rv;
866 }
867 
868 void
l2relay(struct interface_info * ip,struct dhcp_packet * dp,int length,struct packet_ctx * pc)869 l2relay(struct interface_info *ip, struct dhcp_packet *dp, int length,
870     struct packet_ctx *pc)
871 {
872 	struct server_list	*sp;
873 	ssize_t			 dplen;
874 
875 	if (dp->hlen > sizeof(dp->chaddr)) {
876 		log_info("Discarding packet with invalid hlen.");
877 		return;
878 	}
879 
880 	relay_agentinfo(pc, ip, dp->op);
881 
882 	switch (dp->op) {
883 	case BOOTREQUEST:
884 		/* Add the relay agent info asked by the user. */
885 		if ((dplen = relay_agentinfo_append(pc, dp, length)) == -1)
886 			return;
887 
888 		/*
889 		 * Re-send the packet to every interface except the one
890 		 * it came in.
891 		 */
892 		for (sp = servers; sp != NULL; sp = sp->next) {
893 			if (sp->intf == ip)
894 				continue;
895 
896 			log_debug("forwarded BOOTREQUEST for %s to %s",
897 			    print_hw_addr(pc->pc_htype, pc->pc_hlen,
898 			    pc->pc_smac), sp->intf->name);
899 
900 			send_packet(sp->intf, dp, dplen, pc);
901 		}
902 		if (ip != interfaces) {
903 			log_debug("forwarded BOOTREQUEST for %s to %s",
904 			    print_hw_addr(pc->pc_htype, pc->pc_hlen,
905 			    pc->pc_smac), interfaces->name);
906 
907 			send_packet(interfaces, dp, dplen, pc);
908 		}
909 		break;
910 
911 	case BOOTREPLY:
912 		/* Remove relay agent info on offer. */
913 		if ((dplen = relay_agentinfo_remove(pc, dp, length)) == -1)
914 			return;
915 
916 		if (ip != interfaces) {
917 			log_debug("forwarded BOOTREPLY for %s to %s",
918 			    print_hw_addr(pc->pc_htype, pc->pc_hlen,
919 			    pc->pc_dmac), interfaces->name);
920 			send_packet(interfaces, dp, dplen, pc);
921 		}
922 		break;
923 
924 	default:
925 		log_debug("invalid operation type '%d'", dp->op);
926 		return;
927 	}
928 }
929