1171169Smlaier/* $OpenBSD: tftp-proxy.c,v 1.2 2006/12/20 03:33:38 joel Exp $ 2171169Smlaier * 3171169Smlaier * Copyright (c) 2005 DLS Internet Services 4171169Smlaier * Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl> 5171169Smlaier * 6171169Smlaier * Redistribution and use in source and binary forms, with or without 7171169Smlaier * modification, are permitted provided that the following conditions 8171169Smlaier * are met: 9171169Smlaier * 10171169Smlaier * 1. Redistributions of source code must retain the above copyright 11171169Smlaier * notice, this list of conditions and the following disclaimer. 12171169Smlaier * 2. Redistributions in binary form must reproduce the above copyright 13171169Smlaier * notice, this list of conditions and the following disclaimer in the 14171169Smlaier * documentation and/or other materials provided with the distribution. 15171169Smlaier * 3. The name of the author may not be used to endorse or promote products 16171169Smlaier * derived from this software without specific prior written permission. 17171169Smlaier * 18171169Smlaier * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19171169Smlaier * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20171169Smlaier * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21171169Smlaier * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22171169Smlaier * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23171169Smlaier * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24171169Smlaier * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25171169Smlaier * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26171169Smlaier * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27171169Smlaier * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28171169Smlaier */ 29171169Smlaier 30171169Smlaier#include <sys/types.h> 31171169Smlaier#include <sys/ioctl.h> 32171169Smlaier#include <sys/uio.h> 33171169Smlaier#include <unistd.h> 34171169Smlaier 35171169Smlaier#include <netinet/in.h> 36171169Smlaier#include <arpa/inet.h> 37171169Smlaier#include <arpa/tftp.h> 38171169Smlaier#include <sys/socket.h> 39171169Smlaier#include <net/if.h> 40171169Smlaier#include <net/pfvar.h> 41171169Smlaier 42171169Smlaier#include <errno.h> 43171169Smlaier#include <pwd.h> 44171169Smlaier#include <stdio.h> 45171169Smlaier#include <syslog.h> 46171169Smlaier#include <string.h> 47171169Smlaier#include <stdlib.h> 48171169Smlaier 49171169Smlaier#include "filter.h" 50171169Smlaier 51171169Smlaier#define CHROOT_DIR "/var/empty" 52171169Smlaier#define NOPRIV_USER "proxy" 53171169Smlaier 54171169Smlaier#define PF_NAT_PROXY_PORT_LOW 50001 55171169Smlaier#define PF_NAT_PROXY_PORT_HIGH 65535 56171169Smlaier 57171169Smlaier#define DEFTRANSWAIT 2 58171169Smlaier#define NTOP_BUFS 4 59171169Smlaier#define PKTSIZE SEGSIZE+4 60171169Smlaier 61171169Smlaierconst char *opcode(int); 62171169Smlaierconst char *sock_ntop(struct sockaddr *); 63171169Smlaieru_int16_t pick_proxy_port(void); 64171169Smlaierstatic void usage(void); 65171169Smlaier 66171169Smlaierextern char *__progname; 67171169Smlaierchar ntop_buf[NTOP_BUFS][INET6_ADDRSTRLEN]; 68171169Smlaierint verbose = 0; 69171169Smlaier 70171169Smlaierint 71171169Smlaiermain(int argc, char *argv[]) 72171169Smlaier{ 73171169Smlaier int c, fd = 0, on = 1, out_fd = 0, peer, reqsize = 0; 74171169Smlaier int transwait = DEFTRANSWAIT; 75171169Smlaier char *p; 76171169Smlaier struct tftphdr *tp; 77171169Smlaier struct passwd *pw; 78171169Smlaier 79171169Smlaier char cbuf[CMSG_SPACE(sizeof(struct sockaddr_storage))]; 80171169Smlaier char req[PKTSIZE]; 81171169Smlaier struct cmsghdr *cmsg; 82171169Smlaier struct msghdr msg; 83171169Smlaier struct iovec iov; 84171169Smlaier 85171169Smlaier struct sockaddr_storage from, proxy, server, proxy_to_server, s_in; 86171169Smlaier struct sockaddr_in sock_out; 87171169Smlaier socklen_t j; 88171169Smlaier in_port_t bindport; 89171169Smlaier 90171169Smlaier openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); 91171169Smlaier 92171169Smlaier while ((c = getopt(argc, argv, "vw:")) != -1) 93171169Smlaier switch (c) { 94171169Smlaier case 'v': 95171169Smlaier verbose++; 96171169Smlaier break; 97171169Smlaier case 'w': 98171169Smlaier transwait = strtoll(optarg, &p, 10); 99171169Smlaier if (transwait < 1) { 100171169Smlaier syslog(LOG_ERR, "invalid -w value"); 101171169Smlaier exit(1); 102171169Smlaier } 103171169Smlaier break; 104171169Smlaier default: 105171169Smlaier usage(); 106171169Smlaier break; 107171169Smlaier } 108171169Smlaier 109171169Smlaier /* open /dev/pf */ 110171169Smlaier init_filter(NULL, verbose); 111171169Smlaier 112171169Smlaier tzset(); 113171169Smlaier 114171169Smlaier pw = getpwnam(NOPRIV_USER); 115171169Smlaier if (!pw) { 116171169Smlaier syslog(LOG_ERR, "no such user %s: %m", NOPRIV_USER); 117171169Smlaier exit(1); 118171169Smlaier } 119171169Smlaier if (chroot(CHROOT_DIR) || chdir("/")) { 120171169Smlaier syslog(LOG_ERR, "chroot %s: %m", CHROOT_DIR); 121171169Smlaier exit(1); 122171169Smlaier } 123171169Smlaier if (setgroups(1, &pw->pw_gid) || 124171169Smlaier setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 125171169Smlaier setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) { 126171169Smlaier syslog(LOG_ERR, "can't revoke privs: %m"); 127171169Smlaier exit(1); 128171169Smlaier } 129171169Smlaier 130171169Smlaier /* non-blocking io */ 131171169Smlaier if (ioctl(fd, FIONBIO, &on) < 0) { 132171169Smlaier syslog(LOG_ERR, "ioctl(FIONBIO): %m"); 133171169Smlaier exit(1); 134171169Smlaier } 135171169Smlaier 136171169Smlaier if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on)) == -1) { 137171169Smlaier syslog(LOG_ERR, "setsockopt(IP_RECVDSTADDR): %m"); 138171169Smlaier exit(1); 139171169Smlaier } 140171169Smlaier 141171169Smlaier j = sizeof(s_in); 142171169Smlaier if (getsockname(fd, (struct sockaddr *)&s_in, &j) == -1) { 143171169Smlaier syslog(LOG_ERR, "getsockname: %m"); 144171169Smlaier exit(1); 145171169Smlaier } 146171169Smlaier 147171169Smlaier bindport = ((struct sockaddr_in *)&s_in)->sin_port; 148171169Smlaier 149171169Smlaier /* req will be pushed back out at the end, unchanged */ 150171169Smlaier j = sizeof(from); 151171169Smlaier if ((reqsize = recvfrom(fd, req, sizeof(req), MSG_PEEK, 152171169Smlaier (struct sockaddr *)&from, &j)) < 0) { 153171169Smlaier syslog(LOG_ERR, "recvfrom: %m"); 154171169Smlaier exit(1); 155171169Smlaier } 156171169Smlaier 157171169Smlaier bzero(&msg, sizeof(msg)); 158171169Smlaier iov.iov_base = req; 159171169Smlaier iov.iov_len = sizeof(req); 160171169Smlaier msg.msg_name = &from; 161171169Smlaier msg.msg_namelen = sizeof(from); 162171169Smlaier msg.msg_iov = &iov; 163171169Smlaier msg.msg_iovlen = 1; 164171169Smlaier msg.msg_control = cbuf; 165171169Smlaier msg.msg_controllen = CMSG_LEN(sizeof(struct sockaddr_storage)); 166171169Smlaier 167171169Smlaier if (recvmsg(fd, &msg, 0) < 0) { 168171169Smlaier syslog(LOG_ERR, "recvmsg: %m"); 169171169Smlaier exit(1); 170171169Smlaier } 171171169Smlaier 172171169Smlaier close(fd); 173171169Smlaier close(1); 174171169Smlaier 175171169Smlaier peer = socket(from.ss_family, SOCK_DGRAM, 0); 176171169Smlaier if (peer < 0) { 177171169Smlaier syslog(LOG_ERR, "socket: %m"); 178171169Smlaier exit(1); 179171169Smlaier } 180171169Smlaier memset(&s_in, 0, sizeof(s_in)); 181171169Smlaier s_in.ss_family = from.ss_family; 182171169Smlaier s_in.ss_len = from.ss_len; 183171169Smlaier 184171169Smlaier /* get local address if possible */ 185171169Smlaier for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; 186171169Smlaier cmsg = CMSG_NXTHDR(&msg, cmsg)) { 187171169Smlaier if (cmsg->cmsg_level == IPPROTO_IP && 188171169Smlaier cmsg->cmsg_type == IP_RECVDSTADDR) { 189171169Smlaier memcpy(&((struct sockaddr_in *)&s_in)->sin_addr, 190171169Smlaier CMSG_DATA(cmsg), sizeof(struct in_addr)); 191171169Smlaier break; 192171169Smlaier } 193171169Smlaier } 194171169Smlaier 195171169Smlaier if (bind(peer, (struct sockaddr *)&s_in, s_in.ss_len) < 0) { 196171169Smlaier syslog(LOG_ERR, "bind: %m"); 197171169Smlaier exit(1); 198171169Smlaier } 199171169Smlaier if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) { 200171169Smlaier syslog(LOG_ERR, "connect: %m"); 201171169Smlaier exit(1); 202171169Smlaier } 203171169Smlaier 204171169Smlaier tp = (struct tftphdr *)req; 205171169Smlaier if (!(ntohs(tp->th_opcode) == RRQ || ntohs(tp->th_opcode) == WRQ)) { 206171169Smlaier /* not a tftp request, bail */ 207171169Smlaier if (verbose) { 208171169Smlaier syslog(LOG_WARNING, "not a valid tftp request"); 209171169Smlaier exit(1); 210171169Smlaier } else 211171169Smlaier /* exit 0 so inetd doesn't log anything */ 212171169Smlaier exit(0); 213171169Smlaier } 214171169Smlaier 215171169Smlaier j = sizeof(struct sockaddr_storage); 216171169Smlaier if (getsockname(fd, (struct sockaddr *)&proxy, &j) == -1) { 217171169Smlaier syslog(LOG_ERR, "getsockname: %m"); 218171169Smlaier exit(1); 219171169Smlaier } 220171169Smlaier 221171169Smlaier ((struct sockaddr_in *)&proxy)->sin_port = bindport; 222171169Smlaier 223171169Smlaier /* find the un-rdr'd server and port the client wanted */ 224171169Smlaier if (server_lookup((struct sockaddr *)&from, 225171169Smlaier (struct sockaddr *)&proxy, (struct sockaddr *)&server, 226171169Smlaier IPPROTO_UDP) != 0) { 227171169Smlaier syslog(LOG_ERR, "pf connection lookup failed (no rdr?)"); 228171169Smlaier exit(1); 229171169Smlaier } 230171169Smlaier 231171169Smlaier /* establish a new outbound connection to the remote server */ 232171169Smlaier if ((out_fd = socket(((struct sockaddr *)&from)->sa_family, 233171169Smlaier SOCK_DGRAM, IPPROTO_UDP)) < 0) { 234171169Smlaier syslog(LOG_ERR, "couldn't create new socket"); 235171169Smlaier exit(1); 236171169Smlaier } 237171169Smlaier 238171169Smlaier bzero((char *)&sock_out, sizeof(sock_out)); 239171169Smlaier sock_out.sin_family = from.ss_family; 240171169Smlaier sock_out.sin_port = htons(pick_proxy_port()); 241171169Smlaier if (bind(out_fd, (struct sockaddr *)&sock_out, sizeof(sock_out)) < 0) { 242171169Smlaier syslog(LOG_ERR, "couldn't bind to new socket: %m"); 243171169Smlaier exit(1); 244171169Smlaier } 245171169Smlaier 246171169Smlaier if (connect(out_fd, (struct sockaddr *)&server, 247171169Smlaier ((struct sockaddr *)&server)->sa_len) < 0 && errno != EINPROGRESS) { 248171169Smlaier syslog(LOG_ERR, "couldn't connect to remote server: %m"); 249171169Smlaier exit(1); 250171169Smlaier } 251171169Smlaier 252171169Smlaier j = sizeof(struct sockaddr_storage); 253171169Smlaier if ((getsockname(out_fd, (struct sockaddr *)&proxy_to_server, 254171169Smlaier &j)) < 0) { 255171169Smlaier syslog(LOG_ERR, "getsockname: %m"); 256171169Smlaier exit(1); 257171169Smlaier } 258171169Smlaier 259171169Smlaier if (verbose) 260171169Smlaier syslog(LOG_INFO, "%s:%d -> %s:%d/%s:%d -> %s:%d \"%s %s\"", 261171169Smlaier sock_ntop((struct sockaddr *)&from), 262171169Smlaier ntohs(((struct sockaddr_in *)&from)->sin_port), 263171169Smlaier sock_ntop((struct sockaddr *)&proxy), 264171169Smlaier ntohs(((struct sockaddr_in *)&proxy)->sin_port), 265171169Smlaier sock_ntop((struct sockaddr *)&proxy_to_server), 266171169Smlaier ntohs(((struct sockaddr_in *)&proxy_to_server)->sin_port), 267171169Smlaier sock_ntop((struct sockaddr *)&server), 268171169Smlaier ntohs(((struct sockaddr_in *)&server)->sin_port), 269171169Smlaier opcode(ntohs(tp->th_opcode)), 270171169Smlaier tp->th_stuff); 271171169Smlaier 272171169Smlaier /* get ready to add rdr and pass rules */ 273171169Smlaier if (prepare_commit(1) == -1) { 274171169Smlaier syslog(LOG_ERR, "couldn't prepare pf commit"); 275171169Smlaier exit(1); 276171169Smlaier } 277171169Smlaier 278171169Smlaier /* rdr from server to us on our random port -> client on its port */ 279171169Smlaier if (add_rdr(1, (struct sockaddr *)&server, 280171169Smlaier (struct sockaddr *)&proxy_to_server, ntohs(sock_out.sin_port), 281171169Smlaier (struct sockaddr *)&from, 282171169Smlaier ntohs(((struct sockaddr_in *)&from)->sin_port), 283171169Smlaier IPPROTO_UDP) == -1) { 284171169Smlaier syslog(LOG_ERR, "couldn't add rdr"); 285171169Smlaier exit(1); 286171169Smlaier } 287171169Smlaier 288171169Smlaier /* explicitly allow the packets to return back to the client (which pf 289171169Smlaier * will see post-rdr) */ 290171169Smlaier if (add_filter(1, PF_IN, (struct sockaddr *)&server, 291171169Smlaier (struct sockaddr *)&from, 292171169Smlaier ntohs(((struct sockaddr_in *)&from)->sin_port), 293171169Smlaier IPPROTO_UDP) == -1) { 294171169Smlaier syslog(LOG_ERR, "couldn't add pass in"); 295171169Smlaier exit(1); 296171169Smlaier } 297171169Smlaier if (add_filter(1, PF_OUT, (struct sockaddr *)&server, 298171169Smlaier (struct sockaddr *)&from, 299171169Smlaier ntohs(((struct sockaddr_in *)&from)->sin_port), 300171169Smlaier IPPROTO_UDP) == -1) { 301171169Smlaier syslog(LOG_ERR, "couldn't add pass out"); 302171169Smlaier exit(1); 303171169Smlaier } 304171169Smlaier 305171169Smlaier /* and just in case, to pass out from us to the server */ 306171169Smlaier if (add_filter(1, PF_OUT, (struct sockaddr *)&proxy_to_server, 307171169Smlaier (struct sockaddr *)&server, 308171169Smlaier ntohs(((struct sockaddr_in *)&server)->sin_port), 309171169Smlaier IPPROTO_UDP) == -1) { 310171169Smlaier syslog(LOG_ERR, "couldn't add pass out"); 311171169Smlaier exit(1); 312171169Smlaier } 313171169Smlaier 314171169Smlaier if (do_commit() == -1) { 315171169Smlaier syslog(LOG_ERR, "couldn't commit pf rules"); 316171169Smlaier exit(1); 317171169Smlaier } 318171169Smlaier 319171169Smlaier /* forward the initial tftp request and start the insanity */ 320171169Smlaier if (send(out_fd, tp, reqsize, 0) < 0) { 321171169Smlaier syslog(LOG_ERR, "couldn't forward tftp packet: %m"); 322171169Smlaier exit(1); 323171169Smlaier } 324171169Smlaier 325171169Smlaier /* allow the transfer to start to establish a state */ 326171169Smlaier sleep(transwait); 327171169Smlaier 328171169Smlaier /* delete our rdr rule and clean up */ 329171169Smlaier prepare_commit(1); 330171169Smlaier do_commit(); 331171169Smlaier 332171169Smlaier return(0); 333171169Smlaier} 334171169Smlaier 335171169Smlaierconst char * 336171169Smlaieropcode(int code) 337171169Smlaier{ 338171169Smlaier static char str[6]; 339171169Smlaier 340171169Smlaier switch (code) { 341171169Smlaier case 1: 342171169Smlaier (void)snprintf(str, sizeof(str), "RRQ"); 343171169Smlaier break; 344171169Smlaier case 2: 345171169Smlaier (void)snprintf(str, sizeof(str), "WRQ"); 346171169Smlaier break; 347171169Smlaier default: 348171169Smlaier (void)snprintf(str, sizeof(str), "(%d)", code); 349171169Smlaier break; 350171169Smlaier } 351171169Smlaier 352171169Smlaier return (str); 353171169Smlaier} 354171169Smlaier 355171169Smlaierconst char * 356171169Smlaiersock_ntop(struct sockaddr *sa) 357171169Smlaier{ 358171169Smlaier static int n = 0; 359171169Smlaier 360171169Smlaier /* Cycle to next buffer. */ 361171169Smlaier n = (n + 1) % NTOP_BUFS; 362171169Smlaier ntop_buf[n][0] = '\0'; 363171169Smlaier 364171169Smlaier if (sa->sa_family == AF_INET) { 365171169Smlaier struct sockaddr_in *sin = (struct sockaddr_in *)sa; 366171169Smlaier 367171169Smlaier return (inet_ntop(AF_INET, &sin->sin_addr, ntop_buf[n], 368171169Smlaier sizeof ntop_buf[0])); 369171169Smlaier } 370171169Smlaier 371171169Smlaier if (sa->sa_family == AF_INET6) { 372171169Smlaier struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; 373171169Smlaier 374171169Smlaier return (inet_ntop(AF_INET6, &sin6->sin6_addr, ntop_buf[n], 375171169Smlaier sizeof ntop_buf[0])); 376171169Smlaier } 377171169Smlaier 378171169Smlaier return (NULL); 379171169Smlaier} 380171169Smlaier 381171169Smlaieru_int16_t 382171169Smlaierpick_proxy_port(void) 383171169Smlaier{ 384171169Smlaier return (IPPORT_HIFIRSTAUTO + (arc4random() % 385171169Smlaier (IPPORT_HILASTAUTO - IPPORT_HIFIRSTAUTO))); 386171169Smlaier} 387171169Smlaier 388171169Smlaierstatic void 389171169Smlaierusage(void) 390171169Smlaier{ 391171169Smlaier syslog(LOG_ERR, "usage: %s [-v] [-w transwait]", __progname); 392171169Smlaier exit(1); 393171169Smlaier} 394