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