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