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