xref: /openbsd-src/usr.sbin/dhcrelay/dhcrelay.c (revision ac6360cb3956be4f1fcfe441dcf0b72e2f0fd416)
1*ac6360cbSflorian /*	$OpenBSD: dhcrelay.c,v 1.67 2024/08/21 10:35:12 florian Exp $ */
23dd2010fShenning 
348be18b4Shenning /*
413be1508Shenning  * Copyright (c) 2004 Henning Brauer <henning@cvs.openbsd.org>
548be18b4Shenning  * Copyright (c) 1997, 1998, 1999 The Internet Software Consortium.
648be18b4Shenning  * All rights reserved.
748be18b4Shenning  *
848be18b4Shenning  * Redistribution and use in source and binary forms, with or without
948be18b4Shenning  * modification, are permitted provided that the following conditions
1048be18b4Shenning  * are met:
1148be18b4Shenning  *
1248be18b4Shenning  * 1. Redistributions of source code must retain the above copyright
1348be18b4Shenning  *    notice, this list of conditions and the following disclaimer.
1448be18b4Shenning  * 2. Redistributions in binary form must reproduce the above copyright
1548be18b4Shenning  *    notice, this list of conditions and the following disclaimer in the
1648be18b4Shenning  *    documentation and/or other materials provided with the distribution.
1748be18b4Shenning  * 3. Neither the name of The Internet Software Consortium nor the names
1848be18b4Shenning  *    of its contributors may be used to endorse or promote products derived
1948be18b4Shenning  *    from this software without specific prior written permission.
2048be18b4Shenning  *
2148be18b4Shenning  * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
2248be18b4Shenning  * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
2348be18b4Shenning  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
2448be18b4Shenning  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2548be18b4Shenning  * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
2648be18b4Shenning  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2748be18b4Shenning  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2848be18b4Shenning  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
2948be18b4Shenning  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
3048be18b4Shenning  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3148be18b4Shenning  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
3248be18b4Shenning  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3348be18b4Shenning  * SUCH DAMAGE.
3448be18b4Shenning  *
3548be18b4Shenning  * This software has been written for the Internet Software Consortium
3648be18b4Shenning  * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
3748be18b4Shenning  * Enterprises.  To learn more about the Internet Software Consortium,
3848be18b4Shenning  * see ``http://www.vix.com/isc''.  To learn more about Vixie
3948be18b4Shenning  * Enterprises, see ``http://www.vix.com''.
4048be18b4Shenning  */
4148be18b4Shenning 
42f70ef60cSkrw #include <sys/types.h>
43b0e46094Sclaudio #include <sys/ioctl.h>
44f70ef60cSkrw #include <sys/socket.h>
45f70ef60cSkrw 
46f70ef60cSkrw #include <arpa/inet.h>
47f70ef60cSkrw 
48f70ef60cSkrw #include <net/if.h>
49f70ef60cSkrw 
50f70ef60cSkrw #include <errno.h>
517307f9faSjca #include <fcntl.h>
52f70ef60cSkrw #include <netdb.h>
53f70ef60cSkrw #include <paths.h>
54f70ef60cSkrw #include <pwd.h>
55f70ef60cSkrw #include <stdio.h>
56f70ef60cSkrw #include <stdlib.h>
57f70ef60cSkrw #include <string.h>
58f70ef60cSkrw #include <syslog.h>
59579e3f2dSguenther #include <time.h>
60f70ef60cSkrw #include <unistd.h>
61f70ef60cSkrw 
62f70ef60cSkrw #include "dhcp.h"
63f70ef60cSkrw #include "dhcpd.h"
64986dbb4cSkrw #include "log.h"
6548be18b4Shenning 
66d8188208Shenning void	 usage(void);
677307f9faSjca int	 rdaemon(int);
6848be18b4Shenning void	 relay(struct interface_info *, struct dhcp_packet *, int,
69fa3d4f89Srzalamena 	    struct packet_ctx *);
70e4b49721Srzalamena void	 l2relay(struct interface_info *, struct dhcp_packet *, int,
71e4b49721Srzalamena 	    struct packet_ctx *);
7248be18b4Shenning char	*print_hw_addr(int, int, unsigned char *);
73c3d07ec0Sclaudio void	 got_response(struct protocol *);
74b0e46094Sclaudio int	 get_rdomain(char *);
7548be18b4Shenning 
762936f4ebSrzalamena void	 relay_agentinfo(struct packet_ctx *, struct interface_info *, int);
77e4b49721Srzalamena 
78e4b49721Srzalamena int	 relay_agentinfo_cmp(struct packet_ctx *pc, uint8_t *, int);
79e4b49721Srzalamena ssize_t	 relay_agentinfo_append(struct packet_ctx *, struct dhcp_packet *,
80e4b49721Srzalamena 	    size_t);
81e4b49721Srzalamena ssize_t	 relay_agentinfo_remove(struct packet_ctx *, struct dhcp_packet *,
82e4b49721Srzalamena 	    size_t);
834be048dcSreyk 
8448be18b4Shenning time_t cur_time;
8548be18b4Shenning 
8648be18b4Shenning struct interface_info *interfaces = NULL;
87f51f0ea7Srzalamena struct server_list *servers;
88962cae8eSrzalamena struct iflist intflist;
894be048dcSreyk int server_fd;
904be048dcSreyk int oflag;
9148be18b4Shenning 
92e4b49721Srzalamena enum dhcp_relay_mode	 drm = DRM_UNKNOWN;
93e4b49721Srzalamena const char		*rai_circuit = NULL;
94e4b49721Srzalamena const char		*rai_remote = NULL;
95c4c35e14Srzalamena int			 rai_replace = 0;
96e4b49721Srzalamena 
9713be1508Shenning int
9813be1508Shenning main(int argc, char *argv[])
9948be18b4Shenning {
1007307f9faSjca 	int			 ch, devnull = -1, daemonize, opt, rdomain;
1010d2d9a15Shenning 	struct server_list	*sp = NULL;
102a0ba4c4aShenning 	struct passwd		*pw;
103c3d07ec0Sclaudio 	struct sockaddr_in	 laddr;
104e4b49721Srzalamena 	int			 optslen;
10548be18b4Shenning 
106f36a5097Sjca 	daemonize = 1;
107f36a5097Sjca 
108986dbb4cSkrw 	log_init(1, LOG_DAEMON);	/* log to stderr until daemonized */
10948be18b4Shenning 
110962cae8eSrzalamena 	setup_iflist();
111962cae8eSrzalamena 
112c4c35e14Srzalamena 	while ((ch = getopt(argc, argv, "aC:di:oR:r")) != -1) {
1130d2d9a15Shenning 		switch (ch) {
114e4b49721Srzalamena 		case 'C':
115e4b49721Srzalamena 			rai_circuit = optarg;
116e4b49721Srzalamena 			break;
1170d2d9a15Shenning 		case 'd':
118f36a5097Sjca 			daemonize = 0;
1190d2d9a15Shenning 			break;
1200d2d9a15Shenning 		case 'i':
1210d2d9a15Shenning 			if (interfaces != NULL)
122d8188208Shenning 				usage();
123ffc715d7Srzalamena 
124962cae8eSrzalamena 			interfaces = iflist_getbyname(optarg);
125a29cd94bSrzalamena 			if (interfaces == NULL)
126986dbb4cSkrw 				fatalx("interface '%s' not found", optarg);
1270d2d9a15Shenning 			break;
1284be048dcSreyk 		case 'o':
1294be048dcSreyk 			/* add the relay agent information option */
1304be048dcSreyk 			oflag++;
1314be048dcSreyk 			break;
132e4b49721Srzalamena 		case 'R':
133e4b49721Srzalamena 			rai_remote = optarg;
134e4b49721Srzalamena 			break;
135c4c35e14Srzalamena 		case 'r':
136c4c35e14Srzalamena 			rai_replace = 1;
137c4c35e14Srzalamena 			break;
1384be048dcSreyk 
1390d2d9a15Shenning 		default:
140d8188208Shenning 			usage();
1410d2d9a15Shenning 			/* not reached */
14248be18b4Shenning 		}
14348be18b4Shenning 	}
1440d2d9a15Shenning 
1450d2d9a15Shenning 	argc -= optind;
1460d2d9a15Shenning 	argv += optind;
1470d2d9a15Shenning 
1480d2d9a15Shenning 	if (argc < 1)
1490d2d9a15Shenning 		usage();
1500d2d9a15Shenning 
151e4b49721Srzalamena 	if (rai_remote != NULL && rai_circuit == NULL)
152986dbb4cSkrw 		fatalx("you must specify a circuit-id with a remote-id");
153e4b49721Srzalamena 
154e4b49721Srzalamena 	/* Validate that we have space for all suboptions. */
155e4b49721Srzalamena 	if (rai_circuit != NULL) {
156e4b49721Srzalamena 		optslen = 2 + strlen(rai_circuit);
157e4b49721Srzalamena 		if (rai_remote != NULL)
158e4b49721Srzalamena 			optslen += 2 + strlen(rai_remote);
159e4b49721Srzalamena 
160e4b49721Srzalamena 		if (optslen > DHCP_OPTION_MAXLEN)
161986dbb4cSkrw 			fatalx("relay agent information is too long");
162e4b49721Srzalamena 	}
163e4b49721Srzalamena 
1640d2d9a15Shenning 	while (argc > 0) {
165*ac6360cbSflorian 		struct addrinfo hints, *res;
1660d2d9a15Shenning 		struct in_addr		 ia, *iap = NULL;
1670d2d9a15Shenning 
168e4b49721Srzalamena 		if ((sp = calloc(1, sizeof(*sp))) == NULL)
169986dbb4cSkrw 			fatalx("calloc");
170e4b49721Srzalamena 
171*ac6360cbSflorian 		memset(&hints, 0, sizeof(hints));
172*ac6360cbSflorian 		hints.ai_family = AF_INET;
173*ac6360cbSflorian 
174962cae8eSrzalamena 		if ((sp->intf = register_interface(argv[0], got_one,
175962cae8eSrzalamena 		    1)) != NULL) {
176e4b49721Srzalamena 			if (drm == DRM_LAYER3)
177986dbb4cSkrw 				fatalx("don't mix interfaces with hosts");
178e4b49721Srzalamena 
179e4b49721Srzalamena 			if (sp->intf->hw_address.htype == HTYPE_IPSEC_TUNNEL)
18044c85a19Smpi 				fatalx("can't use IPsec with layer 2");
181e4b49721Srzalamena 
182e4b49721Srzalamena 			sp->next = servers;
183e4b49721Srzalamena 			servers = sp;
184e4b49721Srzalamena 
185e4b49721Srzalamena 			drm = DRM_LAYER2;
186e4b49721Srzalamena 			argc--;
187e4b49721Srzalamena 			argv++;
188e4b49721Srzalamena 			continue;
189e4b49721Srzalamena 		}
190e4b49721Srzalamena 
191*ac6360cbSflorian 		if (getaddrinfo(argv[0], NULL, &hints, &res) != 0)
192986dbb4cSkrw 			log_warnx("%s: host unknown", argv[0]);
193*ac6360cbSflorian 		else {
194*ac6360cbSflorian 			ia = ((struct sockaddr_in *)res->ai_addr)->sin_addr;
195*ac6360cbSflorian 			iap = &ia;
196*ac6360cbSflorian 			freeaddrinfo(res);
1970d2d9a15Shenning 		}
198*ac6360cbSflorian 
19948be18b4Shenning 		if (iap) {
200e4b49721Srzalamena 			if (drm == DRM_LAYER2)
201986dbb4cSkrw 				fatalx("don't mix interfaces with hosts");
202e4b49721Srzalamena 
203e4b49721Srzalamena 			drm = DRM_LAYER3;
20448be18b4Shenning 			sp->next = servers;
20548be18b4Shenning 			servers = sp;
206f51f0ea7Srzalamena 			memcpy(&ss2sin(&sp->to)->sin_addr, iap, sizeof(*iap));
207e4b49721Srzalamena 		} else
208e4b49721Srzalamena 			free(sp);
209e4b49721Srzalamena 
2100d2d9a15Shenning 		argc--;
2110d2d9a15Shenning 		argv++;
21248be18b4Shenning 	}
21348be18b4Shenning 
2147307f9faSjca 	if (daemonize) {
215b7041c07Sderaadt 		devnull = open(_PATH_DEVNULL, O_RDWR);
2167307f9faSjca 		if (devnull == -1)
217d8cc3220Skrw 			fatal("open(%s)", _PATH_DEVNULL);
2187307f9faSjca 	}
21948be18b4Shenning 
220962cae8eSrzalamena 	if (interfaces == NULL ||
221962cae8eSrzalamena 	    register_interface(interfaces->name, got_one, 0) == NULL)
222986dbb4cSkrw 		fatalx("no interface given");
223962cae8eSrzalamena 
224e4b49721Srzalamena 	/* We need an address for running layer 3 mode. */
225e4b49721Srzalamena 	if (drm == DRM_LAYER3 &&
226699fe671Srzalamena 	    (interfaces->hw_address.htype != HTYPE_IPSEC_TUNNEL &&
227699fe671Srzalamena 	    interfaces->primary_address.s_addr == 0))
228986dbb4cSkrw 		fatalx("interface '%s' does not have an address",
229a29cd94bSrzalamena 		    interfaces->name);
23048be18b4Shenning 
23148be18b4Shenning 	/* We need at least one server. */
23213be1508Shenning 	if (!sp)
233d8188208Shenning 		usage();
23448be18b4Shenning 
235b0e46094Sclaudio 	rdomain = get_rdomain(interfaces->name);
236b0e46094Sclaudio 
2374be048dcSreyk 	/* Enable the relay agent option by default for enc0 */
2384be048dcSreyk 	if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL)
2394be048dcSreyk 		oflag++;
2404be048dcSreyk 
241c3d07ec0Sclaudio 	bzero(&laddr, sizeof laddr);
242c3d07ec0Sclaudio 	laddr.sin_len = sizeof laddr;
243c3d07ec0Sclaudio 	laddr.sin_family = AF_INET;
244c28564c4Srzalamena 	laddr.sin_port = htons(SERVER_PORT);
245c3d07ec0Sclaudio 	laddr.sin_addr.s_addr = interfaces->primary_address.s_addr;
24648be18b4Shenning 	/* Set up the server sockaddrs. */
24748be18b4Shenning 	for (sp = servers; sp; sp = sp->next) {
248e4b49721Srzalamena 		if (sp->intf != NULL)
249e4b49721Srzalamena 			break;
250e4b49721Srzalamena 
251f51f0ea7Srzalamena 		ss2sin(&sp->to)->sin_port = htons(SERVER_PORT);
252f51f0ea7Srzalamena 		ss2sin(&sp->to)->sin_family = AF_INET;
253f51f0ea7Srzalamena 		ss2sin(&sp->to)->sin_len = sizeof(struct sockaddr_in);
254c3d07ec0Sclaudio 		sp->fd = socket(AF_INET, SOCK_DGRAM, 0);
255c3d07ec0Sclaudio 		if (sp->fd == -1)
256d8cc3220Skrw 			fatal("socket");
257c3d07ec0Sclaudio 		opt = 1;
258c3d07ec0Sclaudio 		if (setsockopt(sp->fd, SOL_SOCKET, SO_REUSEPORT,
259c3d07ec0Sclaudio 		    &opt, sizeof(opt)) == -1)
260d8cc3220Skrw 			fatal("setsockopt");
26113ae2b57Smikeb 		if (setsockopt(sp->fd, SOL_SOCKET, SO_RTABLE, &rdomain,
262b0e46094Sclaudio 		    sizeof(rdomain)) == -1)
263d8cc3220Skrw 			fatal("setsockopt");
2644971ba0eSkrw 		if (bind(sp->fd, (struct sockaddr *)&laddr, sizeof laddr) ==
2654971ba0eSkrw 		    -1)
266d8cc3220Skrw 			fatal("bind");
267c3d07ec0Sclaudio 		if (connect(sp->fd, (struct sockaddr *)&sp->to,
268f51f0ea7Srzalamena 		    sp->to.ss_len) == -1)
269d8cc3220Skrw 			fatal("connect");
270c3d07ec0Sclaudio 		add_protocol("server", sp->fd, got_response, sp);
27148be18b4Shenning 	}
27248be18b4Shenning 
273b0e46094Sclaudio 	/* Socket used to forward packets to the DHCP client */
2744be048dcSreyk 	if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
2754be048dcSreyk 		laddr.sin_addr.s_addr = INADDR_ANY;
2764be048dcSreyk 		server_fd = socket(AF_INET, SOCK_DGRAM, 0);
2774be048dcSreyk 		if (server_fd == -1)
278d8cc3220Skrw 			fatal("socket");
2794be048dcSreyk 		opt = 1;
2804be048dcSreyk 		if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT,
2814be048dcSreyk 		    &opt, sizeof(opt)) == -1)
282d8cc3220Skrw 			fatal("setsockopt");
28313ae2b57Smikeb 		if (setsockopt(server_fd, SOL_SOCKET, SO_RTABLE, &rdomain,
284b0e46094Sclaudio 		    sizeof(rdomain)) == -1)
285d8cc3220Skrw 			fatal("setsockopt");
2864be048dcSreyk 		if (bind(server_fd, (struct sockaddr *)&laddr,
2874be048dcSreyk 		    sizeof(laddr)) == -1)
288d8cc3220Skrw 			fatal("bind");
2894be048dcSreyk 	}
2904be048dcSreyk 
291e4a93d04Shenning 	tzset();
292e4a93d04Shenning 
29348be18b4Shenning 	time(&cur_time);
294e4b49721Srzalamena 	if (drm == DRM_LAYER3)
29548be18b4Shenning 		bootp_packet_handler = relay;
296e4b49721Srzalamena 	else
297e4b49721Srzalamena 		bootp_packet_handler = l2relay;
29848be18b4Shenning 
299a0ba4c4aShenning 	if ((pw = getpwnam("_dhcp")) == NULL)
300986dbb4cSkrw 		fatalx("user \"_dhcp\" not found");
3018552a089Skrw 	if (chroot(pw->pw_dir) == -1)
302d8cc3220Skrw 		fatal("chroot");
303a0ba4c4aShenning 	if (chdir("/") == -1)
304d8cc3220Skrw 		fatal("chdir(\"/\")");
305a0ba4c4aShenning 	if (setgroups(1, &pw->pw_gid) ||
3063e7173e3Sdjm 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
3073e7173e3Sdjm 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
308d8cc3220Skrw 		fatal("can't drop privileges");
309a0ba4c4aShenning 
3107307f9faSjca 	if (daemonize) {
3117307f9faSjca 		if (rdaemon(devnull) == -1)
312d8cc3220Skrw 			fatal("rdaemon");
3132c3d0355Sreyk 
3142c3d0355Sreyk 		log_init(0, LOG_DAEMON);	/* stop logging to stderr */
3157307f9faSjca 	}
3167307f9faSjca 
317d3290b0cSreyk 	if (pledge("stdio route", NULL) == -1)
318986dbb4cSkrw 		fatalx("pledge");
319d3290b0cSreyk 
32048be18b4Shenning 	dispatch();
32113be1508Shenning 	/* not reached */
32248be18b4Shenning 
32313be1508Shenning 	exit(0);
32448be18b4Shenning }
32548be18b4Shenning 
32613be1508Shenning void
32713be1508Shenning relay(struct interface_info *ip, struct dhcp_packet *packet, int length,
328fa3d4f89Srzalamena     struct packet_ctx *pc)
32948be18b4Shenning {
33048be18b4Shenning 	struct server_list	*sp;
33148be18b4Shenning 	struct sockaddr_in	 to;
33248be18b4Shenning 
33348be18b4Shenning 	if (packet->hlen > sizeof packet->chaddr) {
334986dbb4cSkrw 		log_info("Discarding packet with invalid hlen.");
33548be18b4Shenning 		return;
33648be18b4Shenning 	}
33748be18b4Shenning 
33848be18b4Shenning 	/* If it's a bootreply, forward it to the client. */
33948be18b4Shenning 	if (packet->op == BOOTREPLY) {
34012c372e5Srzalamena 		/* Filter packet that were not meant for us. */
34112c372e5Srzalamena 		if (packet->giaddr.s_addr !=
34212c372e5Srzalamena 		    interfaces->primary_address.s_addr)
34312c372e5Srzalamena 			return;
34412c372e5Srzalamena 
34513be1508Shenning 		bzero(&to, sizeof(to));
34648be18b4Shenning 		if (!(packet->flags & htons(BOOTP_BROADCAST))) {
34748be18b4Shenning 			to.sin_addr = packet->yiaddr;
348c28564c4Srzalamena 			to.sin_port = htons(CLIENT_PORT);
34948be18b4Shenning 		} else {
35048be18b4Shenning 			to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
351c28564c4Srzalamena 			to.sin_port = htons(CLIENT_PORT);
35248be18b4Shenning 		}
35348be18b4Shenning 		to.sin_family = AF_INET;
35448be18b4Shenning 		to.sin_len = sizeof to;
355fa3d4f89Srzalamena 		*ss2sin(&pc->pc_dst) = to;
35648be18b4Shenning 
35799e009caSpatrick 		/*
35899e009caSpatrick 		 * Set up the hardware destination address.  If it's a reply
35999e009caSpatrick 		 * with the BROADCAST flag set, we should send an L2 broad-
36099e009caSpatrick 		 * cast as well.
36199e009caSpatrick 		 */
36299e009caSpatrick 		if (!(packet->flags & htons(BOOTP_BROADCAST))) {
363fa3d4f89Srzalamena 			pc->pc_hlen = packet->hlen;
364fa3d4f89Srzalamena 			if (pc->pc_hlen > CHADDR_SIZE)
365fa3d4f89Srzalamena 				pc->pc_hlen = CHADDR_SIZE;
366fa3d4f89Srzalamena 			memcpy(pc->pc_dmac, packet->chaddr, pc->pc_hlen);
367fa3d4f89Srzalamena 			pc->pc_htype = packet->htype;
36899e009caSpatrick 		} else {
369fa3d4f89Srzalamena 			memset(pc->pc_dmac, 0xff, sizeof(pc->pc_dmac));
37099e009caSpatrick 		}
37148be18b4Shenning 
3722936f4ebSrzalamena 		relay_agentinfo(pc, interfaces, packet->op);
373e4b49721Srzalamena 		if ((length = relay_agentinfo_remove(pc, packet,
374e4b49721Srzalamena 		    length)) == -1) {
375986dbb4cSkrw 			log_info("ignoring BOOTREPLY with invalid "
3764be048dcSreyk 			    "relay agent information");
3774be048dcSreyk 			return;
3784be048dcSreyk 		}
3794be048dcSreyk 
38066698dbcSdlg 		/*
38166698dbcSdlg 		 * VMware PXE "ROMs" confuse the DHCP gateway address
38266698dbcSdlg 		 * with the IP gateway address. This is a problem if your
38366698dbcSdlg 		 * DHCP relay is running on something that's not your
38466698dbcSdlg 		 * network gateway.
38566698dbcSdlg 		 *
38666698dbcSdlg 		 * It is purely informational from the relay to the client
38766698dbcSdlg 		 * so we can safely clear it.
38866698dbcSdlg 		 */
38966698dbcSdlg 		packet->giaddr.s_addr = 0x0;
39066698dbcSdlg 
391fa3d4f89Srzalamena 		ss2sin(&pc->pc_src)->sin_addr = interfaces->primary_address;
392fa3d4f89Srzalamena 		if (send_packet(interfaces, packet, length, pc) != -1)
393986dbb4cSkrw 			log_debug("forwarded BOOTREPLY for %s to %s",
39448be18b4Shenning 			    print_hw_addr(packet->htype, packet->hlen,
39513be1508Shenning 			    packet->chaddr), inet_ntoa(to.sin_addr));
39648be18b4Shenning 		return;
39748be18b4Shenning 	}
39848be18b4Shenning 
399c3d07ec0Sclaudio 	if (ip == NULL) {
400986dbb4cSkrw 		log_info("ignoring non BOOTREPLY from server");
401c3d07ec0Sclaudio 		return;
402c3d07ec0Sclaudio 	}
403c3d07ec0Sclaudio 
404b5f87b06Spatrick 	if (packet->hops > 16) {
405986dbb4cSkrw 		log_info("ignoring BOOTREQUEST with hop count of %d",
406b5f87b06Spatrick 		    packet->hops);
40748be18b4Shenning 		return;
40848be18b4Shenning 	}
409b5f87b06Spatrick 	packet->hops++;
41048be18b4Shenning 
411b5f87b06Spatrick 	/*
412b5f87b06Spatrick 	 * Set the giaddr so the server can figure out what net it's
413b5f87b06Spatrick 	 * from and so that we can later forward the response to the
414b5f87b06Spatrick 	 * correct net.  The RFC specifies that we have to keep the
415b5f87b06Spatrick 	 * initial giaddr (in case we relay over multiple hops).
416b5f87b06Spatrick 	 */
417b5f87b06Spatrick 	if (!packet->giaddr.s_addr)
41848be18b4Shenning 		packet->giaddr = ip->primary_address;
41948be18b4Shenning 
4202936f4ebSrzalamena 	relay_agentinfo(pc, interfaces, packet->op);
421e4b49721Srzalamena 	if ((length = relay_agentinfo_append(pc, packet, length)) == -1) {
422986dbb4cSkrw 		log_info("ignoring BOOTREQUEST with invalid "
4234be048dcSreyk 		    "relay agent information");
4244be048dcSreyk 		return;
4254be048dcSreyk 	}
4264be048dcSreyk 
42748be18b4Shenning 	/* Otherwise, it's a BOOTREQUEST, so forward it to all the
42848be18b4Shenning 	   servers. */
42948be18b4Shenning 	for (sp = servers; sp; sp = sp->next) {
430c3d07ec0Sclaudio 		if (send(sp->fd, packet, length, 0) != -1) {
431986dbb4cSkrw 			log_debug("forwarded BOOTREQUEST for %s to %s",
43248be18b4Shenning 			    print_hw_addr(packet->htype, packet->hlen,
433f51f0ea7Srzalamena 			    packet->chaddr),
434f51f0ea7Srzalamena 			    inet_ntoa(ss2sin(&sp->to)->sin_addr));
43548be18b4Shenning 		}
43648be18b4Shenning 	}
43713be1508Shenning 
43848be18b4Shenning }
43948be18b4Shenning 
440d8188208Shenning void
441d8188208Shenning usage(void)
44248be18b4Shenning {
443d8188208Shenning 	extern char	*__progname;
44448be18b4Shenning 
445c4c35e14Srzalamena 	fprintf(stderr, "usage: %s [-dor] [-C circuit-id] [-R remote-id] "
446bf61b054Sjmc 	    "-i interface\n\tdestination ...\n",
4470b52b90cSsobrado 	    __progname);
448d8188208Shenning 	exit(1);
44948be18b4Shenning }
45048be18b4Shenning 
4517307f9faSjca int
4527307f9faSjca rdaemon(int devnull)
4537307f9faSjca {
454ec8c1742Sjca 	if (devnull == -1) {
455ec8c1742Sjca 		errno = EBADF;
456ec8c1742Sjca 		return (-1);
457ec8c1742Sjca 	}
458ec8c1742Sjca 	if (fcntl(devnull, F_GETFL) == -1)
459ec8c1742Sjca 		return (-1);
4607307f9faSjca 
4617307f9faSjca 	switch (fork()) {
4627307f9faSjca 	case -1:
4637307f9faSjca 		return (-1);
4647307f9faSjca 	case 0:
4657307f9faSjca 		break;
4667307f9faSjca 	default:
4677307f9faSjca 		_exit(0);
4687307f9faSjca 	}
4697307f9faSjca 
4707307f9faSjca 	if (setsid() == -1)
4717307f9faSjca 		return (-1);
4727307f9faSjca 
4737307f9faSjca 	(void)dup2(devnull, STDIN_FILENO);
4747307f9faSjca 	(void)dup2(devnull, STDOUT_FILENO);
4757307f9faSjca 	(void)dup2(devnull, STDERR_FILENO);
4767307f9faSjca 	if (devnull > 2)
4777307f9faSjca 		(void)close(devnull);
4787307f9faSjca 
4797307f9faSjca 	return (0);
4807307f9faSjca }
4817307f9faSjca 
48213be1508Shenning char *
48313be1508Shenning print_hw_addr(int htype, int hlen, unsigned char *data)
48448be18b4Shenning {
48548be18b4Shenning 	static char	 habuf[49];
486fd395675Sderaadt 	char		*s = habuf;
487fd395675Sderaadt 	int		 i, j, slen = sizeof(habuf);
48848be18b4Shenning 
489fd395675Sderaadt 	if (htype == 0 || hlen == 0) {
49048be18b4Shenning bad:
49148be18b4Shenning 		strlcpy(habuf, "<null>", sizeof habuf);
49248be18b4Shenning 		return habuf;
493fd395675Sderaadt 	}
49448be18b4Shenning 
495fd395675Sderaadt 	for (i = 0; i < hlen; i++) {
496fd395675Sderaadt 		j = snprintf(s, slen, "%02x", data[i]);
497fd395675Sderaadt 		if (j <= 0 || j >= slen)
498fd395675Sderaadt 			goto bad;
499fd395675Sderaadt 		j = strlen (s);
500fd395675Sderaadt 		s += j;
501fd395675Sderaadt 		slen -= (j + 1);
502fd395675Sderaadt 		*s++ = ':';
503fd395675Sderaadt 	}
504fd395675Sderaadt 	*--s = '\0';
505fd395675Sderaadt 	return habuf;
50648be18b4Shenning }
507c3d07ec0Sclaudio 
508c3d07ec0Sclaudio void
509c3d07ec0Sclaudio got_response(struct protocol *l)
510c3d07ec0Sclaudio {
511fa3d4f89Srzalamena 	struct packet_ctx pc;
512c3d07ec0Sclaudio 	ssize_t result;
513c3d07ec0Sclaudio 	union {
514c3d07ec0Sclaudio 		/*
515c3d07ec0Sclaudio 		 * Packet input buffer.  Must be as large as largest
516c3d07ec0Sclaudio 		 * possible MTU.
517c3d07ec0Sclaudio 		 */
518c3d07ec0Sclaudio 		unsigned char packbuf[4095];
519c3d07ec0Sclaudio 		struct dhcp_packet packet;
520c3d07ec0Sclaudio 	} u;
521c3d07ec0Sclaudio 	struct server_list *sp = l->local;
522c3d07ec0Sclaudio 
5234be048dcSreyk 	memset(&u, DHO_END, sizeof(u));
524c3d07ec0Sclaudio 	if ((result = recv(l->fd, u.packbuf, sizeof(u), 0)) == -1 &&
525c3d07ec0Sclaudio 	    errno != ECONNREFUSED) {
526c3d07ec0Sclaudio 		/*
527abda2296Sbenno 		 * Ignore ECONNREFUSED as too many dhcp servers send a bogus
528c3d07ec0Sclaudio 		 * icmp unreach for every request.
529c3d07ec0Sclaudio 		 */
530f51f0ea7Srzalamena 		log_warn("recv failed for %s",
531f51f0ea7Srzalamena 		    inet_ntoa(ss2sin(&sp->to)->sin_addr));
532c3d07ec0Sclaudio 		return;
533c3d07ec0Sclaudio 	}
5349db3f500Sstevesk 	if (result == -1 && errno == ECONNREFUSED)
5359db3f500Sstevesk 		return;
5369db3f500Sstevesk 
537c3d07ec0Sclaudio 	if (result == 0)
538c3d07ec0Sclaudio 		return;
539c3d07ec0Sclaudio 
540c3d07ec0Sclaudio 	if (result < BOOTP_MIN_LEN) {
541986dbb4cSkrw 		log_info("Discarding packet with invalid size.");
542c3d07ec0Sclaudio 		return;
543c3d07ec0Sclaudio 	}
544c3d07ec0Sclaudio 
545fa3d4f89Srzalamena 	memset(&pc, 0, sizeof(pc));
546fa3d4f89Srzalamena 	pc.pc_src.ss_family = AF_INET;
547fa3d4f89Srzalamena 	pc.pc_src.ss_len = sizeof(struct sockaddr_in);
548f51f0ea7Srzalamena 	memcpy(&ss2sin(&pc.pc_src)->sin_addr, &ss2sin(&sp->to)->sin_addr,
549fa3d4f89Srzalamena 	    sizeof(ss2sin(&pc.pc_src)->sin_addr));
550c28564c4Srzalamena 	ss2sin(&pc.pc_src)->sin_port = htons(SERVER_PORT);
551c3d07ec0Sclaudio 
552fa3d4f89Srzalamena 	pc.pc_dst.ss_family = AF_INET;
553fa3d4f89Srzalamena 	pc.pc_dst.ss_len = sizeof(struct sockaddr_in);
554c28564c4Srzalamena 	ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT);
555fa3d4f89Srzalamena 
556fa3d4f89Srzalamena 	if (bootp_packet_handler)
557fa3d4f89Srzalamena 		(*bootp_packet_handler)(NULL, &u.packet, result, &pc);
558c3d07ec0Sclaudio }
5594be048dcSreyk 
560e4b49721Srzalamena void
5612936f4ebSrzalamena relay_agentinfo(struct packet_ctx *pc, struct interface_info *intf,
5622936f4ebSrzalamena     int bootop)
5634be048dcSreyk {
564e4b49721Srzalamena 	static u_int8_t		 buf[8];
5652936f4ebSrzalamena 	struct sockaddr_in	*sin;
5664be048dcSreyk 
567e4b49721Srzalamena 	if (oflag == 0)
568e4b49721Srzalamena 		return;
5694be048dcSreyk 
570e4b49721Srzalamena 	if (rai_remote != NULL) {
571b2b4b45fSreyk 		pc->pc_remote = rai_remote;
572e4b49721Srzalamena 		pc->pc_remotelen = strlen(rai_remote);
573e4b49721Srzalamena 	} else
574e4b49721Srzalamena 		pc->pc_remotelen = 0;
5754be048dcSreyk 
576e4b49721Srzalamena 	if (rai_circuit == NULL) {
577e4b49721Srzalamena 		buf[0] = (uint8_t)(intf->index << 8);
578e4b49721Srzalamena 		buf[1] = intf->index & 0xff;
579e4b49721Srzalamena 		pc->pc_circuit = buf;
580e4b49721Srzalamena 		pc->pc_circuitlen = 2;
5814be048dcSreyk 
582e4b49721Srzalamena 		if (rai_remote == NULL) {
5832936f4ebSrzalamena 			if (bootop == BOOTREPLY)
5842936f4ebSrzalamena 				sin = ss2sin(&pc->pc_dst);
5852936f4ebSrzalamena 			else
5862936f4ebSrzalamena 				sin = ss2sin(&pc->pc_src);
5872936f4ebSrzalamena 
588e4b49721Srzalamena 			pc->pc_remote =
5892936f4ebSrzalamena 			    (uint8_t *)&sin->sin_addr;
590e4b49721Srzalamena 			pc->pc_remotelen =
5912936f4ebSrzalamena 			    sizeof(sin->sin_addr);
592e4b49721Srzalamena 		}
593e4b49721Srzalamena 	} else {
594b2b4b45fSreyk 		pc->pc_circuit = rai_circuit;
595e4b49721Srzalamena 		pc->pc_circuitlen = strlen(rai_circuit);
596e4b49721Srzalamena 	}
5974be048dcSreyk }
5984be048dcSreyk 
599e4b49721Srzalamena int
600e4b49721Srzalamena relay_agentinfo_cmp(struct packet_ctx *pc, uint8_t *p, int plen)
601e4b49721Srzalamena {
602e4b49721Srzalamena 	int		 len;
603e4b49721Srzalamena 	char		 buf[256];
6044be048dcSreyk 
605e4b49721Srzalamena 	if (oflag == 0)
606e4b49721Srzalamena 		return (-1);
607e4b49721Srzalamena 
608e4b49721Srzalamena 	len = *(p + 1);
609e4b49721Srzalamena 	if (len > plen)
610e4b49721Srzalamena 		return (-1);
611e4b49721Srzalamena 
612e4b49721Srzalamena 	switch (*p) {
613e4b49721Srzalamena 	case RAI_CIRCUIT_ID:
614e4b49721Srzalamena 		if (pc->pc_circuit == NULL)
615e4b49721Srzalamena 			return (-1);
616e4b49721Srzalamena 		if (pc->pc_circuitlen != len)
617e4b49721Srzalamena 			return (-1);
618e4b49721Srzalamena 
619e4b49721Srzalamena 		memcpy(buf, p + DHCP_OPTION_HDR_LEN, len);
620e4b49721Srzalamena 		return (memcmp(pc->pc_circuit, buf, len));
621e4b49721Srzalamena 
622e4b49721Srzalamena 	case RAI_REMOTE_ID:
623e4b49721Srzalamena 		if (pc->pc_remote == NULL)
624e4b49721Srzalamena 			return (-1);
625e4b49721Srzalamena 		if (pc->pc_remotelen != len)
626e4b49721Srzalamena 			return (-1);
627e4b49721Srzalamena 
628e4b49721Srzalamena 		memcpy(buf, p + DHCP_OPTION_HDR_LEN, len);
629e4b49721Srzalamena 		return (memcmp(pc->pc_remote, buf, len));
630e4b49721Srzalamena 
631e4b49721Srzalamena 	default:
632e4b49721Srzalamena 		/* Unmatched type */
633986dbb4cSkrw 		log_info("unmatched relay info %d", *p);
634e4b49721Srzalamena 		return (0);
635e4b49721Srzalamena 	}
636e4b49721Srzalamena }
637e4b49721Srzalamena 
638e4b49721Srzalamena ssize_t
639e4b49721Srzalamena relay_agentinfo_append(struct packet_ctx *pc, struct dhcp_packet *dp,
640e4b49721Srzalamena     size_t dplen)
641e4b49721Srzalamena {
642e4b49721Srzalamena 	uint8_t		*p, *startp;
643e4b49721Srzalamena 	ssize_t		 newtotal = dplen;
644e4b49721Srzalamena 	int		 opttotal, optlen, i, hasinfo = 0;
645e4b49721Srzalamena 	int		 maxlen, neededlen;
646e4b49721Srzalamena 
647e4b49721Srzalamena 	/* Only append when enabled. */
648e4b49721Srzalamena 	if (oflag == 0)
649e4b49721Srzalamena 		return (dplen);
650e4b49721Srzalamena 
651e4b49721Srzalamena 	startp = (uint8_t *)dp;
652e4b49721Srzalamena 	p = (uint8_t *)&dp->options;
653e4b49721Srzalamena 	if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) {
654986dbb4cSkrw 		log_info("invalid dhcp options cookie");
6554be048dcSreyk 		return (-1);
6564be048dcSreyk 	}
657e4b49721Srzalamena 
658e4b49721Srzalamena 	p += DHCP_OPTIONS_COOKIE_LEN;
659e4b49721Srzalamena 	opttotal = dplen - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
660e4b49721Srzalamena 	maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1;
661e4b49721Srzalamena 	if (maxlen < 1 || opttotal < 1)
662e4b49721Srzalamena 		return (dplen);
663e4b49721Srzalamena 
664e4b49721Srzalamena 	for (i = 0; i < opttotal && *p != DHO_END;) {
665e4b49721Srzalamena 		if (*p == DHO_PAD)
666e4b49721Srzalamena 			optlen = 1;
667e4b49721Srzalamena 		else
668e4b49721Srzalamena 			optlen = p[1] + DHCP_OPTION_HDR_LEN;
669e4b49721Srzalamena 
670e4b49721Srzalamena 		if ((i + optlen) > opttotal) {
671986dbb4cSkrw 			log_info("truncated dhcp options");
672e4b49721Srzalamena 			return (-1);
6734be048dcSreyk 		}
6744be048dcSreyk 
675e4b49721Srzalamena 		if (*p == DHO_RELAY_AGENT_INFORMATION) {
676c4c35e14Srzalamena 			if (rai_replace) {
677c4c35e14Srzalamena 				memmove(p, p + optlen, opttotal - i);
678c4c35e14Srzalamena 				opttotal -= optlen;
679c4c35e14Srzalamena 				optlen = 0;
680c4c35e14Srzalamena 			} else
681e4b49721Srzalamena 				hasinfo = 1;
6824be048dcSreyk 		}
6834be048dcSreyk 
684e4b49721Srzalamena 		p += optlen;
685e4b49721Srzalamena 		i += optlen;
6864be048dcSreyk 
687e4b49721Srzalamena 		/* We reached the end, append the relay agent info. */
688e4b49721Srzalamena 		if (i < opttotal && *p == DHO_END) {
689e4b49721Srzalamena 			/* We already have the Relay Agent Info, skip it. */
690e4b49721Srzalamena 			if (hasinfo)
691e4b49721Srzalamena 				continue;
692e4b49721Srzalamena 
693e4b49721Srzalamena 			/* Calculate needed length to append new data. */
694e4b49721Srzalamena 			neededlen = newtotal + DHCP_OPTION_HDR_LEN;
695e4b49721Srzalamena 			if (pc->pc_circuitlen > 0)
696e4b49721Srzalamena 				neededlen += DHCP_OPTION_HDR_LEN +
697e4b49721Srzalamena 				    pc->pc_circuitlen;
698e4b49721Srzalamena 			if (pc->pc_remotelen > 0)
699e4b49721Srzalamena 				neededlen += DHCP_OPTION_HDR_LEN +
700e4b49721Srzalamena 				    pc->pc_remotelen;
701e4b49721Srzalamena 
702e4b49721Srzalamena 			/* Save one byte for DHO_END. */
703e4b49721Srzalamena 			neededlen += 1;
704e4b49721Srzalamena 
7054971ba0eSkrw 			/* Check if we have space for the new options. */
706e4b49721Srzalamena 			if (neededlen > maxlen) {
707986dbb4cSkrw 				log_warnx("no space for relay agent info");
708e4b49721Srzalamena 				return (newtotal);
709e4b49721Srzalamena 			}
710e4b49721Srzalamena 
711e4b49721Srzalamena 			/* New option header: 2 bytes. */
712e4b49721Srzalamena 			newtotal += DHCP_OPTION_HDR_LEN;
713e4b49721Srzalamena 
714e4b49721Srzalamena 			*p++ = DHO_RELAY_AGENT_INFORMATION;
715e4b49721Srzalamena 			*p = 0;
716e4b49721Srzalamena 			if (pc->pc_circuitlen > 0) {
717e4b49721Srzalamena 				newtotal += DHCP_OPTION_HDR_LEN +
718e4b49721Srzalamena 				    pc->pc_circuitlen;
719e4b49721Srzalamena 				*p = (*p) + DHCP_OPTION_HDR_LEN +
720e4b49721Srzalamena 				    pc->pc_circuitlen;
721e4b49721Srzalamena 			}
722e4b49721Srzalamena 
723e4b49721Srzalamena 			if (pc->pc_remotelen > 0) {
724e4b49721Srzalamena 				newtotal += DHCP_OPTION_HDR_LEN +
725e4b49721Srzalamena 				    pc->pc_remotelen;
726e4b49721Srzalamena 				*p = (*p) + DHCP_OPTION_HDR_LEN +
727e4b49721Srzalamena 				    pc->pc_remotelen;
728e4b49721Srzalamena 			}
729e4b49721Srzalamena 
730e4b49721Srzalamena 			p++;
731e4b49721Srzalamena 
732e4b49721Srzalamena 			/* Sub-option circuit-id header plus value. */
733e4b49721Srzalamena 			if (pc->pc_circuitlen > 0) {
734e4b49721Srzalamena 				*p++ = RAI_CIRCUIT_ID;
735e4b49721Srzalamena 				*p++ = pc->pc_circuitlen;
736e4b49721Srzalamena 				memcpy(p, pc->pc_circuit, pc->pc_circuitlen);
737e4b49721Srzalamena 
738e4b49721Srzalamena 				p += pc->pc_circuitlen;
739e4b49721Srzalamena 			}
740e4b49721Srzalamena 
741e4b49721Srzalamena 			/* Sub-option remote-id header plus value. */
742e4b49721Srzalamena 			if (pc->pc_remotelen > 0) {
743e4b49721Srzalamena 				*p++ = RAI_REMOTE_ID;
744e4b49721Srzalamena 				*p++ = pc->pc_remotelen;
745e4b49721Srzalamena 				memcpy(p, pc->pc_remote, pc->pc_remotelen);
746e4b49721Srzalamena 
747e4b49721Srzalamena 				p += pc->pc_remotelen;
748e4b49721Srzalamena 			}
7494be048dcSreyk 
7504be048dcSreyk 			*p = DHO_END;
7514be048dcSreyk 		}
7524be048dcSreyk 	}
7534be048dcSreyk 
754e4b49721Srzalamena 	/* Zero the padding so we don't leak anything. */
755e4b49721Srzalamena 	p++;
756e4b49721Srzalamena 	if (p < (startp + maxlen))
757e4b49721Srzalamena 		memset(p, 0, (startp + maxlen) - p);
758e4b49721Srzalamena 
759e4b49721Srzalamena 	return (newtotal);
760e4b49721Srzalamena }
761e4b49721Srzalamena 
762e4b49721Srzalamena ssize_t
763e4b49721Srzalamena relay_agentinfo_remove(struct packet_ctx *pc, struct dhcp_packet *dp,
764e4b49721Srzalamena     size_t dplen)
765e4b49721Srzalamena {
766e4b49721Srzalamena 	uint8_t		*p, *np, *startp, *endp;
767e4b49721Srzalamena 	int		 opttotal, optleft;
768e4b49721Srzalamena 	int		 suboptlen, optlen, i;
769e4b49721Srzalamena 	int		 maxlen, remaining, matched = 0;
770e4b49721Srzalamena 
771e4b49721Srzalamena 	startp = (uint8_t *)dp;
772e4b49721Srzalamena 	p = (uint8_t *)&dp->options;
773e4b49721Srzalamena 	if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) {
774986dbb4cSkrw 		log_info("invalid dhcp options cookie");
775e4b49721Srzalamena 		return (-1);
776e4b49721Srzalamena 	}
777e4b49721Srzalamena 
778e4b49721Srzalamena 	maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1;
779e4b49721Srzalamena 	opttotal = dplen - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
780e4b49721Srzalamena 	optleft = opttotal;
781e4b49721Srzalamena 
782e4b49721Srzalamena 	p += DHCP_OPTIONS_COOKIE_LEN;
783e4b49721Srzalamena 	endp = p + opttotal;
784e4b49721Srzalamena 
785e4b49721Srzalamena 	for (i = 0; i < opttotal && *p != DHO_END;) {
786e4b49721Srzalamena 		if (*p == DHO_PAD)
787e4b49721Srzalamena 			optlen = 1;
788e4b49721Srzalamena 		else
789e4b49721Srzalamena 			optlen = p[1] + DHCP_OPTION_HDR_LEN;
790e4b49721Srzalamena 
791e4b49721Srzalamena 		if ((i + optlen) > opttotal) {
792986dbb4cSkrw 			log_info("truncated dhcp options");
793e4b49721Srzalamena 			return (-1);
794e4b49721Srzalamena 		}
795e4b49721Srzalamena 
796e4b49721Srzalamena 		if (*p == DHO_RELAY_AGENT_INFORMATION) {
797e4b49721Srzalamena 			/* Fast case: there is no next option. */
798e4b49721Srzalamena 			np = p + optlen;
799e4b49721Srzalamena 			if (*np == DHO_END) {
800e4b49721Srzalamena 				*p = *np;
801e4b49721Srzalamena 				endp = p + 1;
802e4b49721Srzalamena 				/* Zero the padding so we don't leak data. */
803e4b49721Srzalamena 				if (endp < (startp + maxlen))
804e4b49721Srzalamena 					memset(endp, 0,
805e4b49721Srzalamena 					    (startp + maxlen) - endp);
806e4b49721Srzalamena 
807e4b49721Srzalamena 				return (dplen);
808e4b49721Srzalamena 			}
809e4b49721Srzalamena 
810e4b49721Srzalamena 			remaining = optlen;
811e4b49721Srzalamena 			while (remaining > 0) {
812e4b49721Srzalamena 				suboptlen = *(p + 1);
813e4b49721Srzalamena 				remaining -= DHCP_OPTION_HDR_LEN + suboptlen;
814e4b49721Srzalamena 
815e4b49721Srzalamena 				matched = 1;
816e4b49721Srzalamena 				if (relay_agentinfo_cmp(pc, p, suboptlen) == 0)
817e4b49721Srzalamena 					continue;
818e4b49721Srzalamena 
819e4b49721Srzalamena 				matched = 0;
820e4b49721Srzalamena 				break;
821e4b49721Srzalamena 			}
822e4b49721Srzalamena 			/* It is not ours Relay Agent Info, don't remove it. */
823e4b49721Srzalamena 			if (matched == 0)
824e4b49721Srzalamena 				break;
825e4b49721Srzalamena 
826e4b49721Srzalamena 			/* Move the other options on top of this one. */
827e4b49721Srzalamena 			optleft -= optlen;
828e4b49721Srzalamena 			endp -= optlen;
829e4b49721Srzalamena 
830e4b49721Srzalamena 			/* Replace the old agent relay info. */
831e4b49721Srzalamena 			memmove(p, dp, optleft);
832e4b49721Srzalamena 
833e4b49721Srzalamena 			endp++;
834e4b49721Srzalamena 			/* Zero the padding so we don't leak data. */
835e4b49721Srzalamena 			if (endp < (startp + maxlen))
836e4b49721Srzalamena 				memset(endp, 0,
837e4b49721Srzalamena 				    (startp + maxlen) - endp);
838e4b49721Srzalamena 
839e4b49721Srzalamena 			return (endp - startp);
840e4b49721Srzalamena 		}
841e4b49721Srzalamena 
842e4b49721Srzalamena 		p += optlen;
843e4b49721Srzalamena 		i += optlen;
844e4b49721Srzalamena 		optleft -= optlen;
845e4b49721Srzalamena 	}
846e4b49721Srzalamena 
847e4b49721Srzalamena 	return (endp - startp);
8484be048dcSreyk }
849b0e46094Sclaudio 
850b0e46094Sclaudio int
851b0e46094Sclaudio get_rdomain(char *name)
852b0e46094Sclaudio {
853b0e46094Sclaudio 	int rv = 0, s;
854b0e46094Sclaudio 	struct  ifreq ifr;
855b0e46094Sclaudio 
856b0e46094Sclaudio 	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
857d8cc3220Skrw 		fatal("get_rdomain socket");
858b0e46094Sclaudio 
859b0e46094Sclaudio 	bzero(&ifr, sizeof(ifr));
860b0e46094Sclaudio 	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
8618bb39f08Sguenther 	if (ioctl(s, SIOCGIFRDOMAIN, (caddr_t)&ifr) != -1)
862b0e46094Sclaudio 		rv = ifr.ifr_rdomainid;
863b0e46094Sclaudio 
864b0e46094Sclaudio 	close(s);
865b0e46094Sclaudio 	return rv;
866b0e46094Sclaudio }
867e4b49721Srzalamena 
868e4b49721Srzalamena void
869e4b49721Srzalamena l2relay(struct interface_info *ip, struct dhcp_packet *dp, int length,
870e4b49721Srzalamena     struct packet_ctx *pc)
871e4b49721Srzalamena {
872e4b49721Srzalamena 	struct server_list	*sp;
873e4b49721Srzalamena 	ssize_t			 dplen;
874e4b49721Srzalamena 
875e4b49721Srzalamena 	if (dp->hlen > sizeof(dp->chaddr)) {
876986dbb4cSkrw 		log_info("Discarding packet with invalid hlen.");
877e4b49721Srzalamena 		return;
878e4b49721Srzalamena 	}
879e4b49721Srzalamena 
8802936f4ebSrzalamena 	relay_agentinfo(pc, ip, dp->op);
881e4b49721Srzalamena 
882e4b49721Srzalamena 	switch (dp->op) {
883e4b49721Srzalamena 	case BOOTREQUEST:
884e4b49721Srzalamena 		/* Add the relay agent info asked by the user. */
885e4b49721Srzalamena 		if ((dplen = relay_agentinfo_append(pc, dp, length)) == -1)
886e4b49721Srzalamena 			return;
887e4b49721Srzalamena 
888e4b49721Srzalamena 		/*
889e4b49721Srzalamena 		 * Re-send the packet to every interface except the one
890e4b49721Srzalamena 		 * it came in.
891e4b49721Srzalamena 		 */
892e4b49721Srzalamena 		for (sp = servers; sp != NULL; sp = sp->next) {
893e4b49721Srzalamena 			if (sp->intf == ip)
894e4b49721Srzalamena 				continue;
895e4b49721Srzalamena 
896986dbb4cSkrw 			log_debug("forwarded BOOTREQUEST for %s to %s",
897e4b49721Srzalamena 			    print_hw_addr(pc->pc_htype, pc->pc_hlen,
898e4b49721Srzalamena 			    pc->pc_smac), sp->intf->name);
899e4b49721Srzalamena 
900e4b49721Srzalamena 			send_packet(sp->intf, dp, dplen, pc);
901e4b49721Srzalamena 		}
902e4b49721Srzalamena 		if (ip != interfaces) {
903986dbb4cSkrw 			log_debug("forwarded BOOTREQUEST for %s to %s",
904e4b49721Srzalamena 			    print_hw_addr(pc->pc_htype, pc->pc_hlen,
905e4b49721Srzalamena 			    pc->pc_smac), interfaces->name);
906e4b49721Srzalamena 
907e4b49721Srzalamena 			send_packet(interfaces, dp, dplen, pc);
908e4b49721Srzalamena 		}
909e4b49721Srzalamena 		break;
910e4b49721Srzalamena 
911e4b49721Srzalamena 	case BOOTREPLY:
912e4b49721Srzalamena 		/* Remove relay agent info on offer. */
913e4b49721Srzalamena 		if ((dplen = relay_agentinfo_remove(pc, dp, length)) == -1)
914e4b49721Srzalamena 			return;
915e4b49721Srzalamena 
916e4b49721Srzalamena 		if (ip != interfaces) {
917986dbb4cSkrw 			log_debug("forwarded BOOTREPLY for %s to %s",
918e4b49721Srzalamena 			    print_hw_addr(pc->pc_htype, pc->pc_hlen,
919e4b49721Srzalamena 			    pc->pc_dmac), interfaces->name);
920e4b49721Srzalamena 			send_packet(interfaces, dp, dplen, pc);
921e4b49721Srzalamena 		}
922e4b49721Srzalamena 		break;
923e4b49721Srzalamena 
924e4b49721Srzalamena 	default:
925986dbb4cSkrw 		log_debug("invalid operation type '%d'", dp->op);
926e4b49721Srzalamena 		return;
927e4b49721Srzalamena 	}
928e4b49721Srzalamena }
929