GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/dhcrelay/dhcrelay.c Lines: 0 430 0.0 %
Date: 2017-11-07 Branches: 0 270 0.0 %

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