xref: /openbsd-src/usr.sbin/dhcrelay/dhcrelay.c (revision 2b0358df1d88d06ef4139321dd05bd5e05d91eaf)
1 /*	$OpenBSD: dhcrelay.c,v 1.31 2008/07/09 20:08:13 sobrado 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 
44 void	 usage(void);
45 void	 relay(struct interface_info *, struct dhcp_packet *, int,
46 	    unsigned int, struct iaddr, struct hardware *);
47 char	*print_hw_addr(int, int, unsigned char *);
48 void	 got_response(struct protocol *);
49 
50 time_t cur_time;
51 
52 int log_perror = 1;
53 
54 u_int16_t server_port;
55 u_int16_t client_port;
56 int log_priority;
57 struct interface_info *interfaces = NULL;
58 
59 struct server_list {
60 	struct server_list *next;
61 	struct sockaddr_in to;
62 	int fd;
63 } *servers;
64 
65 int
66 main(int argc, char *argv[])
67 {
68 	int			 ch, no_daemon = 0, opt;
69 	extern char		*__progname;
70 	struct server_list	*sp = NULL;
71 	struct passwd		*pw;
72 	struct sockaddr_in	 laddr;
73 
74 	/* Initially, log errors to stderr as well as to syslogd. */
75 	openlog(__progname, LOG_NDELAY, DHCPD_LOG_FACILITY);
76 	setlogmask(LOG_UPTO(LOG_INFO));
77 
78 	while ((ch = getopt(argc, argv, "di:")) != -1) {
79 		switch (ch) {
80 		case 'd':
81 			no_daemon = 1;
82 			break;
83 		case 'i':
84 			if (interfaces != NULL)
85 				usage();
86 			if ((interfaces = calloc(1,
87 			    sizeof(struct interface_info))) == NULL)
88 				error("calloc");
89 			strlcpy(interfaces->name, optarg,
90 			    sizeof(interfaces->name));
91 			break;
92 		default:
93 			usage();
94 			/* not reached */
95 		}
96 	}
97 
98 	argc -= optind;
99 	argv += optind;
100 
101 	if (argc < 1)
102 		usage();
103 
104 	while (argc > 0) {
105 		struct hostent		*he;
106 		struct in_addr		 ia, *iap = NULL;
107 
108 		if (inet_aton(argv[0], &ia))
109 			iap = &ia;
110 		else {
111 			he = gethostbyname(argv[0]);
112 			if (!he)
113 				warning("%s: host unknown", argv[0]);
114 			else
115 				iap = ((struct in_addr *)he->h_addr_list[0]);
116 		}
117 		if (iap) {
118 			if ((sp = calloc(1, sizeof *sp)) == NULL)
119 				error("calloc");
120 			sp->next = servers;
121 			servers = sp;
122 			memcpy(&sp->to.sin_addr, iap, sizeof *iap);
123 		}
124 		argc--;
125 		argv++;
126 	}
127 
128 	log_perror = 0;
129 
130 	if (interfaces == NULL)
131 		error("no interface given");
132 
133 	/* Default DHCP/BOOTP ports. */
134 	server_port = htons(SERVER_PORT);
135 	client_port = htons(CLIENT_PORT);
136 
137 	/* We need at least one server. */
138 	if (!sp)
139 		usage();
140 
141 	discover_interfaces(interfaces);
142 
143 	bzero(&laddr, sizeof laddr);
144 	laddr.sin_len = sizeof laddr;
145 	laddr.sin_family = AF_INET;
146 	laddr.sin_port = server_port;
147 	laddr.sin_addr.s_addr = interfaces->primary_address.s_addr;
148 	/* Set up the server sockaddrs. */
149 	for (sp = servers; sp; sp = sp->next) {
150 		sp->to.sin_port = server_port;
151 		sp->to.sin_family = AF_INET;
152 		sp->to.sin_len = sizeof sp->to;
153 		sp->fd = socket(AF_INET, SOCK_DGRAM, 0);
154 		if (sp->fd == -1)
155 			error("socket: %m");
156 		opt = 1;
157 		if (setsockopt(sp->fd, SOL_SOCKET, SO_REUSEPORT,
158 		    &opt, sizeof(opt)) == -1)
159 			error("setsockopt: %m");
160 		if (bind(sp->fd, (struct sockaddr *)&laddr, sizeof laddr) == -1)
161 			error("bind: %m");
162 		if (connect(sp->fd, (struct sockaddr *)&sp->to,
163 		    sizeof sp->to) == -1)
164 			error("connect: %m");
165 		add_protocol("server", sp->fd, got_response, sp);
166 	}
167 
168 	tzset();
169 
170 	time(&cur_time);
171 	bootp_packet_handler = relay;
172 	if (!no_daemon)
173 		daemon(0, 0);
174 
175 	if ((pw = getpwnam("_dhcp")) == NULL)
176 		error("user \"_dhcp\" not found");
177 	if (chroot(_PATH_VAREMPTY) == -1)
178 		error("chroot: %m");
179 	if (chdir("/") == -1)
180 		error("chdir(\"/\"): %m");
181 	if (setgroups(1, &pw->pw_gid) ||
182 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
183 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
184 		error("can't drop privileges: %m");
185 
186 	dispatch();
187 	/* not reached */
188 
189 	exit(0);
190 }
191 
192 void
193 relay(struct interface_info *ip, struct dhcp_packet *packet, int length,
194     unsigned int from_port, struct iaddr from, struct hardware *hfrom)
195 {
196 	struct server_list	*sp;
197 	struct sockaddr_in	 to;
198 	struct hardware		 hto;
199 
200 	if (packet->hlen > sizeof packet->chaddr) {
201 		note("Discarding packet with invalid hlen.");
202 		return;
203 	}
204 
205 	/* If it's a bootreply, forward it to the client. */
206 	if (packet->op == BOOTREPLY) {
207 		bzero(&to, sizeof(to));
208 		if (!(packet->flags & htons(BOOTP_BROADCAST))) {
209 			to.sin_addr = packet->yiaddr;
210 			to.sin_port = client_port;
211 		} else {
212 			to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
213 			to.sin_port = client_port;
214 		}
215 		to.sin_family = AF_INET;
216 		to.sin_len = sizeof to;
217 
218 		/* Set up the hardware destination address. */
219 		hto.hlen = packet->hlen;
220 		if (hto.hlen > sizeof hto.haddr)
221 			hto.hlen = sizeof hto.haddr;
222 		memcpy(hto.haddr, packet->chaddr, hto.hlen);
223 		hto.htype = packet->htype;
224 
225 		if (send_packet(interfaces, packet, length,
226 		    interfaces->primary_address, &to, &hto) != -1)
227 			debug("forwarded BOOTREPLY for %s to %s",
228 			    print_hw_addr(packet->htype, packet->hlen,
229 			    packet->chaddr), inet_ntoa(to.sin_addr));
230 		return;
231 	}
232 
233 	if (ip == NULL) {
234 		note("ignoring non BOOTREPLY from server");
235 		return;
236 	}
237 
238 	/* If giaddr is set on a BOOTREQUEST, ignore it - it's already
239 	   been gatewayed. */
240 	if (packet->giaddr.s_addr) {
241 		note("ignoring BOOTREQUEST with giaddr of %s",
242 		    inet_ntoa(packet->giaddr));
243 		return;
244 	}
245 
246 	/* Set the giaddr so the server can figure out what net it's
247 	   from and so that we can later forward the response to the
248 	   correct net. */
249 	packet->giaddr = ip->primary_address;
250 
251 	/* Otherwise, it's a BOOTREQUEST, so forward it to all the
252 	   servers. */
253 	for (sp = servers; sp; sp = sp->next) {
254 		if (send(sp->fd, packet, length, 0) != -1) {
255 			debug("forwarded BOOTREQUEST for %s to %s",
256 			    print_hw_addr(packet->htype, packet->hlen,
257 			    packet->chaddr), inet_ntoa(sp->to.sin_addr));
258 		}
259 	}
260 
261 }
262 
263 void
264 usage(void)
265 {
266 	extern char	*__progname;
267 
268 	fprintf(stderr, "usage: %s [-d] -i interface server1 [... serverN]\n",
269 	    __progname);
270 	exit(1);
271 }
272 
273 char *
274 print_hw_addr(int htype, int hlen, unsigned char *data)
275 {
276 	static char	 habuf[49];
277 	char		*s = habuf;
278 	int		 i, j, slen = sizeof(habuf);
279 
280 	if (htype == 0 || hlen == 0) {
281 bad:
282 		strlcpy(habuf, "<null>", sizeof habuf);
283 		return habuf;
284 	}
285 
286 	for (i = 0; i < hlen; i++) {
287 		j = snprintf(s, slen, "%02x", data[i]);
288 		if (j <= 0 || j >= slen)
289 			goto bad;
290 		j = strlen (s);
291 		s += j;
292 		slen -= (j + 1);
293 		*s++ = ':';
294 	}
295 	*--s = '\0';
296 	return habuf;
297 }
298 
299 void
300 got_response(struct protocol *l)
301 {
302 	ssize_t result;
303 	struct iaddr ifrom;
304 	union {
305 		/*
306 		 * Packet input buffer.  Must be as large as largest
307 		 * possible MTU.
308 		 */
309 		unsigned char packbuf[4095];
310 		struct dhcp_packet packet;
311 	} u;
312 	struct server_list *sp = l->local;
313 
314 	if ((result = recv(l->fd, u.packbuf, sizeof(u), 0)) == -1 &&
315 	    errno != ECONNREFUSED) {
316 		/*
317 		 * Ignore ECONNREFUSED as to many dhcp server send a bogus
318 		 * icmp unreach for every request.
319 		 */
320 		warning("recv failed for %s: %m",
321 		    inet_ntoa(sp->to.sin_addr));
322 		return;
323 	}
324 	if (result == -1 && errno == ECONNREFUSED)
325 		return;
326 
327 	if (result == 0)
328 		return;
329 
330 	if (result < BOOTP_MIN_LEN) {
331 		note("Discarding packet with invalid size.");
332 		return;
333 	}
334 
335 	if (bootp_packet_handler) {
336 		ifrom.len = 4;
337 		memcpy(ifrom.iabuf, &sp->to.sin_addr, ifrom.len);
338 
339 		(*bootp_packet_handler)(NULL, &u.packet, result,
340 		    sp->to.sin_port, ifrom, NULL);
341 	}
342 }
343