xref: /freebsd/contrib/pf/tftp-proxy/tftp-proxy.c (revision 61e21613)
1 /* $OpenBSD: tftp-proxy.c,v 1.2 2006/12/20 03:33:38 joel Exp $
2  *
3  * Copyright (c) 2005 DLS Internet Services
4  * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. The name of the author may not be used to endorse or promote products
16  *    derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <sys/types.h>
31 #include <sys/ioctl.h>
32 #include <sys/uio.h>
33 #include <unistd.h>
34 
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
37 #include <arpa/tftp.h>
38 #include <sys/socket.h>
39 #include <net/if.h>
40 #include <net/pfvar.h>
41 
42 #include <errno.h>
43 #include <pwd.h>
44 #include <stdio.h>
45 #include <syslog.h>
46 #include <string.h>
47 #include <stdlib.h>
48 
49 #include "filter.h"
50 
51 #define CHROOT_DIR	"/var/empty"
52 #define NOPRIV_USER	"proxy"
53 
54 #define PF_NAT_PROXY_PORT_LOW	50001
55 #define PF_NAT_PROXY_PORT_HIGH	65535
56 
57 #define DEFTRANSWAIT	2
58 #define NTOP_BUFS	4
59 #define PKTSIZE		SEGSIZE+4
60 
61 const char *opcode(int);
62 const char *sock_ntop(struct sockaddr *);
63 u_int16_t pick_proxy_port(void);
64 static void usage(void);
65 
66 extern	char *__progname;
67 char	ntop_buf[NTOP_BUFS][INET6_ADDRSTRLEN];
68 int	verbose = 0;
69 
70 int
71 main(int argc, char *argv[])
72 {
73 	int c, fd = 0, on = 1, out_fd = 0, peer, reqsize = 0;
74 	int transwait = DEFTRANSWAIT;
75 	char *p;
76 	struct tftphdr *tp;
77 	struct passwd *pw;
78 
79 	char cbuf[CMSG_SPACE(sizeof(struct sockaddr_storage))];
80 	char req[PKTSIZE];
81 	struct cmsghdr *cmsg;
82 	struct msghdr msg;
83 	struct iovec iov;
84 
85 	struct sockaddr_storage from, proxy, server, proxy_to_server, s_in;
86 	struct sockaddr_in sock_out;
87 	socklen_t j;
88 	in_port_t bindport;
89 
90 	openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
91 
92 	while ((c = getopt(argc, argv, "vw:")) != -1)
93 		switch (c) {
94 		case 'v':
95 			verbose++;
96 			break;
97 		case 'w':
98 			transwait = strtoll(optarg, &p, 10);
99 			if (transwait < 1) {
100 				syslog(LOG_ERR, "invalid -w value");
101 				exit(1);
102 			}
103 			break;
104 		default:
105 			usage();
106 			break;
107 		}
108 
109 	/* open /dev/pf */
110 	init_filter(NULL, verbose);
111 
112 	tzset();
113 
114 	pw = getpwnam(NOPRIV_USER);
115 	if (!pw) {
116 		syslog(LOG_ERR, "no such user %s: %m", NOPRIV_USER);
117 		exit(1);
118 	}
119 	if (chroot(CHROOT_DIR) || chdir("/")) {
120 		syslog(LOG_ERR, "chroot %s: %m", CHROOT_DIR);
121 		exit(1);
122 	}
123 	if (setgroups(1, &pw->pw_gid) ||
124 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
125 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) {
126 		syslog(LOG_ERR, "can't revoke privs: %m");
127 		exit(1);
128 	}
129 
130 	/* non-blocking io */
131 	if (ioctl(fd, FIONBIO, &on) < 0) {
132 		syslog(LOG_ERR, "ioctl(FIONBIO): %m");
133 		exit(1);
134 	}
135 
136 	if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)) == -1) {
137 		syslog(LOG_ERR, "setsockopt(IP_RECVDSTADDR): %m");
138 		exit(1);
139 	}
140 
141 	j = sizeof(s_in);
142 	if (getsockname(fd, (struct sockaddr *)&s_in, &j) == -1) {
143 		syslog(LOG_ERR, "getsockname: %m");
144 		exit(1);
145 	}
146 
147 	bindport = ((struct sockaddr_in *)&s_in)->sin_port;
148 
149 	/* req will be pushed back out at the end, unchanged */
150 	j = sizeof(from);
151 	if ((reqsize = recvfrom(fd, req, sizeof(req), MSG_PEEK,
152 	    (struct sockaddr *)&from, &j)) < 0) {
153 		syslog(LOG_ERR, "recvfrom: %m");
154 		exit(1);
155 	}
156 
157 	bzero(&msg, sizeof(msg));
158 	iov.iov_base = req;
159 	iov.iov_len = sizeof(req);
160 	msg.msg_name = &from;
161 	msg.msg_namelen = sizeof(from);
162 	msg.msg_iov = &iov;
163 	msg.msg_iovlen = 1;
164 	msg.msg_control = cbuf;
165 	msg.msg_controllen = CMSG_LEN(sizeof(struct sockaddr_storage));
166 
167 	if (recvmsg(fd, &msg, 0) < 0) {
168 		syslog(LOG_ERR, "recvmsg: %m");
169 		exit(1);
170 	}
171 
172 	close(fd);
173 	close(1);
174 
175 	peer = socket(from.ss_family, SOCK_DGRAM, 0);
176 	if (peer < 0) {
177 		syslog(LOG_ERR, "socket: %m");
178 		exit(1);
179 	}
180 	memset(&s_in, 0, sizeof(s_in));
181 	s_in.ss_family = from.ss_family;
182 	s_in.ss_len = from.ss_len;
183 
184 	/* get local address if possible */
185 	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
186 	    cmsg = CMSG_NXTHDR(&msg, cmsg)) {
187 		if (cmsg->cmsg_level == IPPROTO_IP &&
188 		    cmsg->cmsg_type == IP_RECVDSTADDR) {
189 			memcpy(&((struct sockaddr_in *)&s_in)->sin_addr,
190 			    CMSG_DATA(cmsg), sizeof(struct in_addr));
191 			break;
192 		}
193 	}
194 
195 	if (bind(peer, (struct sockaddr *)&s_in, s_in.ss_len) < 0) {
196 		syslog(LOG_ERR, "bind: %m");
197 		exit(1);
198 	}
199 	if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) {
200 		syslog(LOG_ERR, "connect: %m");
201 		exit(1);
202 	}
203 
204 	tp = (struct tftphdr *)req;
205 	if (!(ntohs(tp->th_opcode) == RRQ || ntohs(tp->th_opcode) == WRQ)) {
206 		/* not a tftp request, bail */
207 		if (verbose) {
208 			syslog(LOG_WARNING, "not a valid tftp request");
209 			exit(1);
210 		} else
211 			/* exit 0 so inetd doesn't log anything */
212 			exit(0);
213 	}
214 
215 	j = sizeof(struct sockaddr_storage);
216 	if (getsockname(fd, (struct sockaddr *)&proxy, &j) == -1) {
217 		syslog(LOG_ERR, "getsockname: %m");
218 		exit(1);
219 	}
220 
221 	((struct sockaddr_in *)&proxy)->sin_port = bindport;
222 
223 	/* find the un-rdr'd server and port the client wanted */
224 	if (server_lookup((struct sockaddr *)&from,
225 	    (struct sockaddr *)&proxy, (struct sockaddr *)&server,
226 	    IPPROTO_UDP) != 0) {
227 		syslog(LOG_ERR, "pf connection lookup failed (no rdr?)");
228 		exit(1);
229 	}
230 
231 	/* establish a new outbound connection to the remote server */
232 	if ((out_fd = socket(((struct sockaddr *)&from)->sa_family,
233 	    SOCK_DGRAM, IPPROTO_UDP)) < 0) {
234 		syslog(LOG_ERR, "couldn't create new socket");
235 		exit(1);
236 	}
237 
238 	bzero((char *)&sock_out, sizeof(sock_out));
239 	sock_out.sin_family = from.ss_family;
240 	sock_out.sin_port = htons(pick_proxy_port());
241 	if (bind(out_fd, (struct sockaddr *)&sock_out, sizeof(sock_out)) < 0) {
242 		syslog(LOG_ERR, "couldn't bind to new socket: %m");
243 		exit(1);
244 	}
245 
246 	if (connect(out_fd, (struct sockaddr *)&server,
247 	    ((struct sockaddr *)&server)->sa_len) < 0 && errno != EINPROGRESS) {
248 		syslog(LOG_ERR, "couldn't connect to remote server: %m");
249 		exit(1);
250 	}
251 
252 	j = sizeof(struct sockaddr_storage);
253 	if ((getsockname(out_fd, (struct sockaddr *)&proxy_to_server,
254 	    &j)) < 0) {
255 		syslog(LOG_ERR, "getsockname: %m");
256 		exit(1);
257 	}
258 
259 	if (verbose)
260 		syslog(LOG_INFO, "%s:%d -> %s:%d/%s:%d -> %s:%d \"%s %s\"",
261 			sock_ntop((struct sockaddr *)&from),
262 			ntohs(((struct sockaddr_in *)&from)->sin_port),
263 			sock_ntop((struct sockaddr *)&proxy),
264 			ntohs(((struct sockaddr_in *)&proxy)->sin_port),
265 			sock_ntop((struct sockaddr *)&proxy_to_server),
266 			ntohs(((struct sockaddr_in *)&proxy_to_server)->sin_port),
267 			sock_ntop((struct sockaddr *)&server),
268 			ntohs(((struct sockaddr_in *)&server)->sin_port),
269 			opcode(ntohs(tp->th_opcode)),
270 			tp->th_stuff);
271 
272 	/* get ready to add rdr and pass rules */
273 	if (prepare_commit(1) == -1) {
274 		syslog(LOG_ERR, "couldn't prepare pf commit");
275 		exit(1);
276 	}
277 
278 	/* rdr from server to us on our random port -> client on its port */
279 	if (add_rdr(1, (struct sockaddr *)&server,
280 	    (struct sockaddr *)&proxy_to_server, ntohs(sock_out.sin_port),
281 	    (struct sockaddr *)&from,
282 	    ntohs(((struct sockaddr_in *)&from)->sin_port),
283 	    IPPROTO_UDP) == -1) {
284 		syslog(LOG_ERR, "couldn't add rdr");
285 		exit(1);
286 	}
287 
288 	/* explicitly allow the packets to return back to the client (which pf
289 	 * will see post-rdr) */
290 	if (add_filter(1, PF_IN, (struct sockaddr *)&server,
291 	    (struct sockaddr *)&from,
292 	    ntohs(((struct sockaddr_in *)&from)->sin_port),
293 	    IPPROTO_UDP) == -1) {
294 		syslog(LOG_ERR, "couldn't add pass in");
295 		exit(1);
296 	}
297 	if (add_filter(1, PF_OUT, (struct sockaddr *)&server,
298 	    (struct sockaddr *)&from,
299 	    ntohs(((struct sockaddr_in *)&from)->sin_port),
300 	    IPPROTO_UDP) == -1) {
301 		syslog(LOG_ERR, "couldn't add pass out");
302 		exit(1);
303 	}
304 
305 	/* and just in case, to pass out from us to the server */
306 	if (add_filter(1, PF_OUT, (struct sockaddr *)&proxy_to_server,
307 	    (struct sockaddr *)&server,
308 	    ntohs(((struct sockaddr_in *)&server)->sin_port),
309 	    IPPROTO_UDP) == -1) {
310 		syslog(LOG_ERR, "couldn't add pass out");
311 		exit(1);
312 	}
313 
314 	if (do_commit() == -1) {
315 		syslog(LOG_ERR, "couldn't commit pf rules");
316 		exit(1);
317 	}
318 
319 	/* forward the initial tftp request and start the insanity */
320 	if (send(out_fd, tp, reqsize, 0) < 0) {
321 		syslog(LOG_ERR, "couldn't forward tftp packet: %m");
322 		exit(1);
323 	}
324 
325 	/* allow the transfer to start to establish a state */
326 	sleep(transwait);
327 
328 	/* delete our rdr rule and clean up */
329 	prepare_commit(1);
330 	do_commit();
331 
332 	return(0);
333 }
334 
335 const char *
336 opcode(int code)
337 {
338 	static char str[6];
339 
340 	switch (code) {
341 	case 1:
342 		(void)snprintf(str, sizeof(str), "RRQ");
343 		break;
344 	case 2:
345 		(void)snprintf(str, sizeof(str), "WRQ");
346 		break;
347 	default:
348 		(void)snprintf(str, sizeof(str), "(%d)", code);
349 		break;
350 	}
351 
352 	return (str);
353 }
354 
355 const char *
356 sock_ntop(struct sockaddr *sa)
357 {
358 	static int n = 0;
359 
360 	/* Cycle to next buffer. */
361 	n = (n + 1) % NTOP_BUFS;
362 	ntop_buf[n][0] = '\0';
363 
364 	if (sa->sa_family == AF_INET) {
365 		struct sockaddr_in *sin = (struct sockaddr_in *)sa;
366 
367 		return (inet_ntop(AF_INET, &sin->sin_addr, ntop_buf[n],
368 		    sizeof ntop_buf[0]));
369 	}
370 
371 	if (sa->sa_family == AF_INET6) {
372 		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
373 
374 		return (inet_ntop(AF_INET6, &sin6->sin6_addr, ntop_buf[n],
375 		    sizeof ntop_buf[0]));
376 	}
377 
378 	return (NULL);
379 }
380 
381 u_int16_t
382 pick_proxy_port(void)
383 {
384 	return (IPPORT_HIFIRSTAUTO + (arc4random() %
385 	    (IPPORT_HILASTAUTO - IPPORT_HIFIRSTAUTO)));
386 }
387 
388 static void
389 usage(void)
390 {
391 	syslog(LOG_ERR, "usage: %s [-v] [-w transwait]", __progname);
392 	exit(1);
393 }
394