1331722Seadler/* 21592Srgrimes * Copyright (c) 1983, 1993 31592Srgrimes * The Regents of the University of California. All rights reserved. 41592Srgrimes * 51592Srgrimes * Redistribution and use in source and binary forms, with or without 61592Srgrimes * modification, are permitted provided that the following conditions 71592Srgrimes * are met: 81592Srgrimes * 1. Redistributions of source code must retain the above copyright 91592Srgrimes * notice, this list of conditions and the following disclaimer. 101592Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 111592Srgrimes * notice, this list of conditions and the following disclaimer in the 121592Srgrimes * documentation and/or other materials provided with the distribution. 13262136Sbrueffer * 3. Neither the name of the University nor the names of its contributors 141592Srgrimes * may be used to endorse or promote products derived from this software 151592Srgrimes * without specific prior written permission. 161592Srgrimes * 171592Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 181592Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 191592Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 201592Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 211592Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 221592Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 231592Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 241592Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 251592Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 261592Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 271592Srgrimes * SUCH DAMAGE. 281592Srgrimes */ 291592Srgrimes 301592Srgrimes#ifndef lint 3131512Scharnierstatic const char copyright[] = 321592Srgrimes"@(#) Copyright (c) 1983, 1993\n\ 331592Srgrimes The Regents of the University of California. All rights reserved.\n"; 341592Srgrimes#endif /* not lint */ 351592Srgrimes 361592Srgrimes#ifndef lint 3731512Scharnier#if 0 381592Srgrimesstatic char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93"; 3931512Scharnier#endif 401592Srgrimes#endif /* not lint */ 41207608Simp#include <sys/cdefs.h> 42207608Simp__FBSDID("$FreeBSD: stable/11/libexec/tftpd/tftpd.c 339051 2018-10-01 15:47:34Z asomers $"); 431592Srgrimes 441592Srgrimes/* 451592Srgrimes * Trivial file transfer protocol server. 461592Srgrimes * 471592Srgrimes * This version includes many modifications by Jim Guyton 481592Srgrimes * <guyton@rand-unix>. 491592Srgrimes */ 501592Srgrimes 511592Srgrimes#include <sys/param.h> 521592Srgrimes#include <sys/ioctl.h> 531592Srgrimes#include <sys/stat.h> 541592Srgrimes#include <sys/socket.h> 551592Srgrimes 561592Srgrimes#include <netinet/in.h> 571592Srgrimes#include <arpa/tftp.h> 581592Srgrimes 591592Srgrimes#include <ctype.h> 601592Srgrimes#include <errno.h> 611592Srgrimes#include <fcntl.h> 621592Srgrimes#include <netdb.h> 6331512Scharnier#include <pwd.h> 64246139Smarius#include <stdint.h> 651592Srgrimes#include <stdio.h> 661592Srgrimes#include <stdlib.h> 671592Srgrimes#include <string.h> 681592Srgrimes#include <syslog.h> 691592Srgrimes#include <unistd.h> 701592Srgrimes 71207608Simp#include "tftp-file.h" 72207608Simp#include "tftp-io.h" 73207608Simp#include "tftp-utils.h" 74207608Simp#include "tftp-transfer.h" 75207608Simp#include "tftp-options.h" 761592Srgrimes 77313231Sngie#ifdef LIBWRAP 78313231Sngie#include <tcpd.h> 79313231Sngie#endif 80313231Sngie 81207608Simpstatic void tftp_wrq(int peer, char *, ssize_t); 82207608Simpstatic void tftp_rrq(int peer, char *, ssize_t); 831592Srgrimes 841592Srgrimes/* 851592Srgrimes * Null-terminated directory prefix list for absolute pathname requests and 861592Srgrimes * search list for relative pathname requests. 871592Srgrimes * 881592Srgrimes * MAXDIRS should be at least as large as the number of arguments that 891592Srgrimes * inetd allows (currently 20). 901592Srgrimes */ 911592Srgrimes#define MAXDIRS 20 921592Srgrimesstatic struct dirlist { 93112452Sdwmalone const char *name; 941592Srgrimes int len; 951592Srgrimes} dirs[MAXDIRS+1]; 961592Srgrimesstatic int suppress_naks; 971592Srgrimesstatic int logging; 9871616Sbillfstatic int ipchroot; 99129680Smdoddstatic int create_new = 0; 100213099Smariusstatic const char *newfile_format = "%Y%m%d"; 101173852Sedwinstatic int increase_name = 0; 102207608Simpstatic mode_t mask = S_IWGRP | S_IWOTH; 1031592Srgrimes 104207608Simpstruct formats; 105207608Simpstatic void tftp_recvfile(int peer, const char *mode); 106207608Simpstatic void tftp_xmitfile(int peer, const char *mode); 107207608Simpstatic int validate_access(int peer, char **, int); 108207608Simpstatic char peername[NI_MAXHOST]; 1091592Srgrimes 110241720Sedstatic FILE *file; 111112452Sdwmalone 112241720Sedstatic struct formats { 113207608Simp const char *f_mode; 114207608Simp int f_convert; 115207608Simp} formats[] = { 116207608Simp { "netascii", 1 }, 117207608Simp { "octet", 0 }, 118207608Simp { NULL, 0 } 119207608Simp}; 120207608Simp 1211592Srgrimesint 12290333Simpmain(int argc, char *argv[]) 1231592Srgrimes{ 12490333Simp struct tftphdr *tp; 125207608Simp int peer; 126207608Simp socklen_t peerlen, len; 127207608Simp ssize_t n; 128207608Simp int ch; 129207608Simp char *chroot_dir = NULL; 130207608Simp struct passwd *nobody; 131207608Simp const char *chuser = "nobody"; 132207608Simp char recvbuffer[MAXPKTSIZE]; 133207608Simp int allow_ro = 1, allow_wo = 1; 1341592Srgrimes 135130839Sbrian tzset(); /* syslog in localtime */ 136207608Simp acting_as_client = 0; 137130839Sbrian 138207608Simp tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 139207608Simp while ((ch = getopt(argc, argv, "cCd:F:lnoOp:s:u:U:wW")) != -1) { 1401592Srgrimes switch (ch) { 14171616Sbillf case 'c': 14271616Sbillf ipchroot = 1; 14371616Sbillf break; 14471616Sbillf case 'C': 14571616Sbillf ipchroot = 2; 14671616Sbillf break; 147207608Simp case 'd': 148207608Simp if (atoi(optarg) != 0) 149207608Simp debug += atoi(optarg); 150207608Simp else 151207608Simp debug |= debug_finds(optarg); 152207608Simp break; 153173852Sedwin case 'F': 154173852Sedwin newfile_format = optarg; 155173852Sedwin break; 1561592Srgrimes case 'l': 1571592Srgrimes logging = 1; 1581592Srgrimes break; 1591592Srgrimes case 'n': 1601592Srgrimes suppress_naks = 1; 1611592Srgrimes break; 162207608Simp case 'o': 163207608Simp options_rfc_enabled = 0; 164207608Simp break; 165207608Simp case 'O': 166207608Simp options_extra_enabled = 0; 167207608Simp break; 168207608Simp case 'p': 169207608Simp packetdroppercentage = atoi(optarg); 170207608Simp tftp_log(LOG_INFO, 171207608Simp "Randomly dropping %d out of 100 packets", 172207608Simp packetdroppercentage); 173207608Simp break; 17418458Simp case 's': 17518458Simp chroot_dir = optarg; 17618458Simp break; 17765850Swollman case 'u': 17865850Swollman chuser = optarg; 17965850Swollman break; 180129680Smdodd case 'U': 181129680Smdodd mask = strtol(optarg, NULL, 0); 182129680Smdodd break; 183129680Smdodd case 'w': 184129680Smdodd create_new = 1; 185129680Smdodd break; 186173852Sedwin case 'W': 187173852Sedwin create_new = 1; 188173852Sedwin increase_name = 1; 189173852Sedwin break; 1901592Srgrimes default: 191207608Simp tftp_log(LOG_WARNING, 192207608Simp "ignoring unknown option -%c", ch); 1931592Srgrimes } 1941592Srgrimes } 1951592Srgrimes if (optind < argc) { 1961592Srgrimes struct dirlist *dirp; 1971592Srgrimes 1981592Srgrimes /* Get list of directory prefixes. Skip relative pathnames. */ 1991592Srgrimes for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; 2001592Srgrimes optind++) { 2011592Srgrimes if (argv[optind][0] == '/') { 2021592Srgrimes dirp->name = argv[optind]; 2031592Srgrimes dirp->len = strlen(dirp->name); 2041592Srgrimes dirp++; 2051592Srgrimes } 2061592Srgrimes } 2071592Srgrimes } 20818458Simp else if (chroot_dir) { 20918458Simp dirs->name = "/"; 21018458Simp dirs->len = 1; 21118458Simp } 212113714Sbillf if (ipchroot > 0 && chroot_dir == NULL) { 213207608Simp tftp_log(LOG_ERR, "-c requires -s"); 21471616Sbillf exit(1); 21571616Sbillf } 2161592Srgrimes 217129680Smdodd umask(mask); 218129680Smdodd 219207608Simp { 220207608Simp int on = 1; 221207608Simp if (ioctl(0, FIONBIO, &on) < 0) { 222207608Simp tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno)); 223207608Simp exit(1); 224207608Simp } 2251592Srgrimes } 226207608Simp 227207608Simp /* Find out who we are talking to and what we are going to do */ 228207608Simp peerlen = sizeof(peer_sock); 229207608Simp n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, 230207608Simp (struct sockaddr *)&peer_sock, &peerlen); 2311592Srgrimes if (n < 0) { 232207608Simp tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno)); 2331592Srgrimes exit(1); 2341592Srgrimes } 235207608Simp getnameinfo((struct sockaddr *)&peer_sock, peer_sock.ss_len, 236207608Simp peername, sizeof(peername), NULL, 0, NI_NUMERICHOST); 237207608Simp 2381592Srgrimes /* 2391592Srgrimes * Now that we have read the message out of the UDP 2401592Srgrimes * socket, we fork and exit. Thus, inetd will go back 2411592Srgrimes * to listening to the tftp port, and the next request 2421592Srgrimes * to come in will start up a new instance of tftpd. 2431592Srgrimes * 2441592Srgrimes * We do this so that inetd can run tftpd in "wait" mode. 2451592Srgrimes * The problem with tftpd running in "nowait" mode is that 2461592Srgrimes * inetd may get one or more successful "selects" on the 2471592Srgrimes * tftp port before we do our receive, so more than one 2481592Srgrimes * instance of tftpd may be started up. Worse, if tftpd 2491592Srgrimes * break before doing the above "recvfrom", inetd would 2501592Srgrimes * spawn endless instances, clogging the system. 2511592Srgrimes */ 2521592Srgrimes { 253141922Sstefanf int i, pid; 2541592Srgrimes 2551592Srgrimes for (i = 1; i < 20; i++) { 2561592Srgrimes pid = fork(); 2571592Srgrimes if (pid < 0) { 2581592Srgrimes sleep(i); 2591592Srgrimes /* 2601592Srgrimes * flush out to most recently sent request. 2611592Srgrimes * 2621592Srgrimes * This may drop some request, but those 2631592Srgrimes * will be resent by the clients when 2641592Srgrimes * they timeout. The positive effect of 2651592Srgrimes * this flush is to (try to) prevent more 2661592Srgrimes * than one tftpd being started up to service 2671592Srgrimes * a single request from a single client. 2681592Srgrimes */ 269207608Simp peerlen = sizeof peer_sock; 270207608Simp i = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, 271207608Simp (struct sockaddr *)&peer_sock, &peerlen); 2721592Srgrimes if (i > 0) { 2731592Srgrimes n = i; 2741592Srgrimes } 2751592Srgrimes } else { 2761592Srgrimes break; 2771592Srgrimes } 2781592Srgrimes } 2791592Srgrimes if (pid < 0) { 280207608Simp tftp_log(LOG_ERR, "fork: %s", strerror(errno)); 2811592Srgrimes exit(1); 2821592Srgrimes } else if (pid != 0) { 2831592Srgrimes exit(0); 2841592Srgrimes } 2851592Srgrimes } 28618458Simp 287313231Sngie#ifdef LIBWRAP 28818458Simp /* 289207608Simp * See if the client is allowed to talk to me. 290207608Simp * (This needs to be done before the chroot()) 291207608Simp */ 292207608Simp { 293207608Simp struct request_info req; 294207608Simp 295207608Simp request_init(&req, RQ_CLIENT_ADDR, peername, 0); 296207608Simp request_set(&req, RQ_DAEMON, "tftpd", 0); 297207608Simp 298207608Simp if (hosts_access(&req) == 0) { 299207608Simp if (debug&DEBUG_ACCESS) 300207608Simp tftp_log(LOG_WARNING, 301207608Simp "Access denied by 'tftpd' entry " 302207608Simp "in /etc/hosts.allow"); 303207608Simp 304207608Simp /* 305207608Simp * Full access might be disabled, but maybe the 306207608Simp * client is allowed to do read-only access. 307207608Simp */ 308207608Simp request_set(&req, RQ_DAEMON, "tftpd-ro", 0); 309207608Simp allow_ro = hosts_access(&req); 310207608Simp 311207608Simp request_set(&req, RQ_DAEMON, "tftpd-wo", 0); 312207608Simp allow_wo = hosts_access(&req); 313207608Simp 314207608Simp if (allow_ro == 0 && allow_wo == 0) { 315207608Simp tftp_log(LOG_WARNING, 316207608Simp "Unauthorized access from %s", peername); 317207608Simp exit(1); 318207608Simp } 319207608Simp 320207608Simp if (debug&DEBUG_ACCESS) { 321207608Simp if (allow_ro) 322207608Simp tftp_log(LOG_WARNING, 323207608Simp "But allowed readonly access " 324207608Simp "via 'tftpd-ro' entry"); 325207608Simp if (allow_wo) 326207608Simp tftp_log(LOG_WARNING, 327207608Simp "But allowed writeonly access " 328207608Simp "via 'tftpd-wo' entry"); 329207608Simp } 330207608Simp } else 331207608Simp if (debug&DEBUG_ACCESS) 332207608Simp tftp_log(LOG_WARNING, 333207608Simp "Full access allowed" 334207608Simp "in /etc/hosts.allow"); 335207608Simp } 336313231Sngie#endif 337207608Simp 338207608Simp /* 33918458Simp * Since we exit here, we should do that only after the above 34018458Simp * recvfrom to keep inetd from constantly forking should there 34118458Simp * be a problem. See the above comment about system clogging. 34218458Simp */ 34318458Simp if (chroot_dir) { 344113714Sbillf if (ipchroot > 0) { 34571616Sbillf char *tempchroot; 34671616Sbillf struct stat sb; 34771616Sbillf int statret; 34894443Sume struct sockaddr_storage ss; 34994443Sume char hbuf[NI_MAXHOST]; 35071616Sbillf 351207608Simp statret = -1; 352207608Simp memcpy(&ss, &peer_sock, peer_sock.ss_len); 35394443Sume unmappedaddr((struct sockaddr_in6 *)&ss); 35494443Sume getnameinfo((struct sockaddr *)&ss, ss.ss_len, 35594443Sume hbuf, sizeof(hbuf), NULL, 0, 356146187Sume NI_NUMERICHOST); 35794443Sume asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); 358113714Sbillf if (ipchroot == 2) 359113714Sbillf statret = stat(tempchroot, &sb); 360113714Sbillf if (ipchroot == 1 || 361113714Sbillf (statret == 0 && (sb.st_mode & S_IFDIR))) 36271616Sbillf chroot_dir = tempchroot; 36371616Sbillf } 36418458Simp /* Must get this before chroot because /etc might go away */ 36565850Swollman if ((nobody = getpwnam(chuser)) == NULL) { 366207608Simp tftp_log(LOG_ERR, "%s: no such user", chuser); 36718458Simp exit(1); 36818458Simp } 36918458Simp if (chroot(chroot_dir)) { 370207608Simp tftp_log(LOG_ERR, "chroot: %s: %s", 371207608Simp chroot_dir, strerror(errno)); 37218458Simp exit(1); 37318458Simp } 374131358Scsjp chdir("/"); 375339051Sasomers if (setgroups(1, &nobody->pw_gid) != 0) { 376339051Sasomers tftp_log(LOG_ERR, "setgroups failed"); 377339051Sasomers exit(1); 378339051Sasomers } 379241848Seadler if (setuid(nobody->pw_uid) != 0) { 380241848Seadler tftp_log(LOG_ERR, "setuid failed"); 381241848Seadler exit(1); 382241848Seadler } 38318458Simp } 38418458Simp 385207608Simp len = sizeof(me_sock); 386207608Simp if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) { 387207608Simp switch (me_sock.ss_family) { 38894443Sume case AF_INET: 389207608Simp ((struct sockaddr_in *)&me_sock)->sin_port = 0; 39094443Sume break; 39194443Sume case AF_INET6: 392207608Simp ((struct sockaddr_in6 *)&me_sock)->sin6_port = 0; 39394443Sume break; 39494443Sume default: 39594443Sume /* unsupported */ 39694443Sume break; 39794443Sume } 39894443Sume } else { 399207608Simp memset(&me_sock, 0, sizeof(me_sock)); 400207608Simp me_sock.ss_family = peer_sock.ss_family; 401207608Simp me_sock.ss_len = peer_sock.ss_len; 40294443Sume } 4031592Srgrimes close(0); 4041592Srgrimes close(1); 405207608Simp peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0); 4061592Srgrimes if (peer < 0) { 407207608Simp tftp_log(LOG_ERR, "socket: %s", strerror(errno)); 4081592Srgrimes exit(1); 4091592Srgrimes } 410207608Simp if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) { 411207608Simp tftp_log(LOG_ERR, "bind: %s", strerror(errno)); 4121592Srgrimes exit(1); 4131592Srgrimes } 414207608Simp 415207608Simp tp = (struct tftphdr *)recvbuffer; 416207608Simp tp->th_opcode = ntohs(tp->th_opcode); 417207608Simp if (tp->th_opcode == RRQ) { 418207608Simp if (allow_ro) 419207608Simp tftp_rrq(peer, tp->th_stuff, n - 1); 420207608Simp else { 421207608Simp tftp_log(LOG_WARNING, 422207608Simp "%s read access denied", peername); 423207608Simp exit(1); 424207608Simp } 425332609Sasomers } else if (tp->th_opcode == WRQ) { 426207608Simp if (allow_wo) 427207608Simp tftp_wrq(peer, tp->th_stuff, n - 1); 428207608Simp else { 429207608Simp tftp_log(LOG_WARNING, 430207608Simp "%s write access denied", peername); 431207608Simp exit(1); 432207608Simp } 433332609Sasomers } else 434332609Sasomers send_error(peer, EBADOP); 4351592Srgrimes exit(1); 4361592Srgrimes} 4371592Srgrimes 438130834Sbrianstatic void 439130834Sbrianreduce_path(char *fn) 440130834Sbrian{ 441130834Sbrian char *slash, *ptr; 442130834Sbrian 443130834Sbrian /* Reduce all "/+./" to "/" (just in case we've got "/./../" later */ 444130834Sbrian while ((slash = strstr(fn, "/./")) != NULL) { 445130834Sbrian for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 446130834Sbrian ; 447130834Sbrian slash += 2; 448130834Sbrian while (*slash) 449130834Sbrian *++ptr = *++slash; 450130834Sbrian } 451130834Sbrian 452130834Sbrian /* Now reduce all "/something/+../" to "/" */ 453130834Sbrian while ((slash = strstr(fn, "/../")) != NULL) { 454130834Sbrian if (slash == fn) 455130834Sbrian break; 456130834Sbrian for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 457130834Sbrian ; 458130834Sbrian for (ptr--; ptr >= fn; ptr--) 459130834Sbrian if (*ptr == '/') 460130834Sbrian break; 461130834Sbrian if (ptr < fn) 462130834Sbrian break; 463130834Sbrian slash += 3; 464130834Sbrian while (*slash) 465130834Sbrian *++ptr = *++slash; 466130834Sbrian } 467130834Sbrian} 468130834Sbrian 469207608Simpstatic char * 470207608Simpparse_header(int peer, char *recvbuffer, ssize_t size, 471207608Simp char **filename, char **mode) 472207608Simp{ 473207608Simp char *cp; 474207608Simp int i; 475207608Simp struct formats *pf; 4761592Srgrimes 477207608Simp *mode = NULL; 478207608Simp cp = recvbuffer; 4791592Srgrimes 480207608Simp i = get_field(peer, recvbuffer, size); 481207608Simp if (i >= PATH_MAX) { 482207608Simp tftp_log(LOG_ERR, "Bad option - filename too long"); 483207608Simp send_error(peer, EBADOP); 484207608Simp exit(1); 485207608Simp } 486207608Simp *filename = recvbuffer; 487207608Simp tftp_log(LOG_INFO, "Filename: '%s'", *filename); 488207608Simp cp += i; 48984047Sobrien 490207608Simp i = get_field(peer, cp, size); 491207608Simp *mode = cp; 492207608Simp cp += i; 49384047Sobrien 494207608Simp /* Find the file transfer mode */ 495207608Simp for (cp = *mode; *cp; cp++) 496207608Simp if (isupper(*cp)) 497207608Simp *cp = tolower(*cp); 498207608Simp for (pf = formats; pf->f_mode; pf++) 499207608Simp if (strcmp(pf->f_mode, *mode) == 0) 500207608Simp break; 501207608Simp if (pf->f_mode == NULL) { 502207608Simp tftp_log(LOG_ERR, 503207608Simp "Bad option - Unknown transfer mode (%s)", *mode); 504207608Simp send_error(peer, EBADOP); 505207608Simp exit(1); 506207608Simp } 507207608Simp tftp_log(LOG_INFO, "Mode: '%s'", *mode); 508207608Simp 509207608Simp return (cp + 1); 510207608Simp} 511207608Simp 5121592Srgrimes/* 513207608Simp * WRQ - receive a file from the client 5141592Srgrimes */ 5151592Srgrimesvoid 516207608Simptftp_wrq(int peer, char *recvbuffer, ssize_t size) 5171592Srgrimes{ 51890333Simp char *cp; 519207608Simp int has_options = 0, ecode; 520207608Simp char *filename, *mode; 521141922Sstefanf char fnbuf[PATH_MAX]; 5221592Srgrimes 523207608Simp cp = parse_header(peer, recvbuffer, size, &filename, &mode); 524207608Simp size -= (cp - recvbuffer) + 1; 525207608Simp 526339051Sasomers strlcpy(fnbuf, filename, sizeof(fnbuf)); 527207608Simp reduce_path(fnbuf); 528207608Simp filename = fnbuf; 529207608Simp 530207608Simp if (size > 0) { 531207608Simp if (options_rfc_enabled) 532207608Simp has_options = !parse_options(peer, cp, size); 533207608Simp else 534207608Simp tftp_log(LOG_INFO, "Options found but not enabled"); 5351592Srgrimes } 536207608Simp 537207608Simp ecode = validate_access(peer, &filename, WRQ); 538207608Simp if (ecode == 0) { 539207608Simp if (has_options) 540207608Simp send_oack(peer); 541207608Simp else 542207608Simp send_ack(peer, 0); 5431592Srgrimes } 544207608Simp if (logging) { 545207608Simp tftp_log(LOG_INFO, "%s: write request for %s: %s", peername, 546207608Simp filename, errtomsg(ecode)); 547122916Ssobomax } 548207608Simp 549332609Sasomers if (ecode) { 550332609Sasomers send_error(peer, ecode); 551332609Sasomers exit(1); 552332609Sasomers } 553207608Simp tftp_recvfile(peer, mode); 554207608Simp exit(0); 555207608Simp} 556207608Simp 557207608Simp/* 558207608Simp * RRQ - send a file to the client 559207608Simp */ 560207608Simpvoid 561207608Simptftp_rrq(int peer, char *recvbuffer, ssize_t size) 562207608Simp{ 563207608Simp char *cp; 564207608Simp int has_options = 0, ecode; 565207608Simp char *filename, *mode; 566207608Simp char fnbuf[PATH_MAX]; 567207608Simp 568207608Simp cp = parse_header(peer, recvbuffer, size, &filename, &mode); 569207608Simp size -= (cp - recvbuffer) + 1; 570207608Simp 571339051Sasomers strlcpy(fnbuf, filename, sizeof(fnbuf)); 572130834Sbrian reduce_path(fnbuf); 573122916Ssobomax filename = fnbuf; 574207608Simp 575207608Simp if (size > 0) { 576207608Simp if (options_rfc_enabled) 577207608Simp has_options = !parse_options(peer, cp, size); 578207608Simp else 579207608Simp tftp_log(LOG_INFO, "Options found but not enabled"); 5801592Srgrimes } 581207608Simp 582207608Simp ecode = validate_access(peer, &filename, RRQ); 583207608Simp if (ecode == 0) { 584207608Simp if (has_options) { 585207608Simp int n; 586207608Simp char lrecvbuffer[MAXPKTSIZE]; 587207608Simp struct tftphdr *rp = (struct tftphdr *)lrecvbuffer; 588207608Simp 589207608Simp send_oack(peer); 590207608Simp n = receive_packet(peer, lrecvbuffer, MAXPKTSIZE, 591207608Simp NULL, timeoutpacket); 592207608Simp if (n < 0) { 593207608Simp if (debug&DEBUG_SIMPLE) 594207608Simp tftp_log(LOG_DEBUG, "Aborting: %s", 595207608Simp rp_strerror(n)); 596207608Simp return; 59784047Sobrien } 598207608Simp if (rp->th_opcode != ACK) { 599207608Simp if (debug&DEBUG_SIMPLE) 600207608Simp tftp_log(LOG_DEBUG, 601207608Simp "Expected ACK, got %s on OACK", 602207608Simp packettype(rp->th_opcode)); 603207608Simp return; 604207608Simp } 60584047Sobrien } 60684047Sobrien } 60784047Sobrien 608207608Simp if (logging) 609207608Simp tftp_log(LOG_INFO, "%s: read request for %s: %s", peername, 610207608Simp filename, errtomsg(ecode)); 61145393Sbrian 6121592Srgrimes if (ecode) { 6131592Srgrimes /* 6141592Srgrimes * Avoid storms of naks to a RRQ broadcast for a relative 6151592Srgrimes * bootfile pathname from a diskless Sun. 6161592Srgrimes */ 6171592Srgrimes if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 6181592Srgrimes exit(0); 619207608Simp send_error(peer, ecode); 6201592Srgrimes exit(1); 6211592Srgrimes } 622207608Simp tftp_xmitfile(peer, mode); 6231592Srgrimes} 6241592Srgrimes 6251592Srgrimes/* 626173852Sedwin * Find the next value for YYYYMMDD.nn when the file to be written should 627173852Sedwin * be unique. Due to the limitations of nn, we will fail if nn reaches 100. 628173852Sedwin * Besides, that is four updates per hour on a file, which is kind of 629173852Sedwin * execessive anyway. 630173852Sedwin */ 631173852Sedwinstatic int 632173852Sedwinfind_next_name(char *filename, int *fd) 633173852Sedwin{ 634173852Sedwin int i; 635173852Sedwin time_t tval; 636173852Sedwin size_t len; 637173852Sedwin struct tm lt; 638173852Sedwin char yyyymmdd[MAXPATHLEN]; 639173852Sedwin char newname[MAXPATHLEN]; 640173852Sedwin 641173852Sedwin /* Create the YYYYMMDD part of the filename */ 642173852Sedwin time(&tval); 643173852Sedwin lt = *localtime(&tval); 644173852Sedwin len = strftime(yyyymmdd, sizeof(yyyymmdd), newfile_format, <); 645173852Sedwin if (len == 0) { 646173852Sedwin syslog(LOG_WARNING, 647173852Sedwin "Filename suffix too long (%d characters maximum)", 648173852Sedwin MAXPATHLEN); 649173852Sedwin return (EACCESS); 650173852Sedwin } 651173852Sedwin 652173852Sedwin /* Make sure the new filename is not too long */ 653173852Sedwin if (strlen(filename) > MAXPATHLEN - len - 5) { 654173852Sedwin syslog(LOG_WARNING, 655207608Simp "Filename too long (%zd characters, %zd maximum)", 656173852Sedwin strlen(filename), MAXPATHLEN - len - 5); 657173852Sedwin return (EACCESS); 658173852Sedwin } 659173852Sedwin 660173852Sedwin /* Find the first file which doesn't exist */ 661173852Sedwin for (i = 0; i < 100; i++) { 662173852Sedwin sprintf(newname, "%s.%s.%02d", filename, yyyymmdd, i); 663173852Sedwin *fd = open(newname, 664173852Sedwin O_WRONLY | O_CREAT | O_EXCL, 665173852Sedwin S_IRUSR | S_IWUSR | S_IRGRP | 666173852Sedwin S_IWGRP | S_IROTH | S_IWOTH); 667173852Sedwin if (*fd > 0) 668173852Sedwin return 0; 669173852Sedwin } 670173852Sedwin 671173852Sedwin return (EEXIST); 672173852Sedwin} 673173852Sedwin 674173852Sedwin/* 6751592Srgrimes * Validate file access. Since we 6761592Srgrimes * have no uid or gid, for now require 6771592Srgrimes * file to exist and be publicly 6781592Srgrimes * readable/writable. 6791592Srgrimes * If we were invoked with arguments 6801592Srgrimes * from inetd then the file must also be 6811592Srgrimes * in one of the given directory prefixes. 6821592Srgrimes * Note also, full path name must be 6831592Srgrimes * given as we have no login directory. 6841592Srgrimes */ 6851592Srgrimesint 686207608Simpvalidate_access(int peer, char **filep, int mode) 6871592Srgrimes{ 6881592Srgrimes struct stat stbuf; 6891592Srgrimes int fd; 690173852Sedwin int error; 6911592Srgrimes struct dirlist *dirp; 6921592Srgrimes static char pathname[MAXPATHLEN]; 6931592Srgrimes char *filename = *filep; 6941592Srgrimes 6951592Srgrimes /* 6961592Srgrimes * Prevent tricksters from getting around the directory restrictions 6971592Srgrimes */ 6981592Srgrimes if (strstr(filename, "/../")) 6991592Srgrimes return (EACCESS); 7001592Srgrimes 7011592Srgrimes if (*filename == '/') { 7021592Srgrimes /* 7031592Srgrimes * Allow the request if it's in one of the approved locations. 7041592Srgrimes * Special case: check the null prefix ("/") by looking 7051592Srgrimes * for length = 1 and relying on the arg. processing that 7061592Srgrimes * it's a /. 7071592Srgrimes */ 7081592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 7091592Srgrimes if (dirp->len == 1 || 7101592Srgrimes (!strncmp(filename, dirp->name, dirp->len) && 7111592Srgrimes filename[dirp->len] == '/')) 7121592Srgrimes break; 7131592Srgrimes } 7141592Srgrimes /* If directory list is empty, allow access to any file */ 7151592Srgrimes if (dirp->name == NULL && dirp != dirs) 7161592Srgrimes return (EACCESS); 7171592Srgrimes if (stat(filename, &stbuf) < 0) 7181592Srgrimes return (errno == ENOENT ? ENOTFOUND : EACCESS); 7191592Srgrimes if ((stbuf.st_mode & S_IFMT) != S_IFREG) 7201592Srgrimes return (ENOTFOUND); 7211592Srgrimes if (mode == RRQ) { 7221592Srgrimes if ((stbuf.st_mode & S_IROTH) == 0) 7231592Srgrimes return (EACCESS); 7241592Srgrimes } else { 7251592Srgrimes if ((stbuf.st_mode & S_IWOTH) == 0) 7261592Srgrimes return (EACCESS); 7271592Srgrimes } 7281592Srgrimes } else { 7291592Srgrimes int err; 7301592Srgrimes 7311592Srgrimes /* 7321592Srgrimes * Relative file name: search the approved locations for it. 7336750Sjkh * Don't allow write requests that avoid directory 7341592Srgrimes * restrictions. 7351592Srgrimes */ 7361592Srgrimes 7376750Sjkh if (!strncmp(filename, "../", 3)) 7381592Srgrimes return (EACCESS); 7391592Srgrimes 7401592Srgrimes /* 7411592Srgrimes * If the file exists in one of the directories and isn't 7421592Srgrimes * readable, continue looking. However, change the error code 7431592Srgrimes * to give an indication that the file exists. 7441592Srgrimes */ 7451592Srgrimes err = ENOTFOUND; 7461592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 74724193Simp snprintf(pathname, sizeof(pathname), "%s/%s", 74824193Simp dirp->name, filename); 7491592Srgrimes if (stat(pathname, &stbuf) == 0 && 7501592Srgrimes (stbuf.st_mode & S_IFMT) == S_IFREG) { 751332609Sasomers if (mode == RRQ) { 752332609Sasomers if ((stbuf.st_mode & S_IROTH) != 0) 753332609Sasomers break; 754332609Sasomers } else { 755332609Sasomers if ((stbuf.st_mode & S_IWOTH) != 0) 756332609Sasomers break; 7571592Srgrimes } 7581592Srgrimes err = EACCESS; 7591592Srgrimes } 7601592Srgrimes } 761129680Smdodd if (dirp->name != NULL) 762129680Smdodd *filep = filename = pathname; 763129680Smdodd else if (mode == RRQ) 7641592Srgrimes return (err); 765332609Sasomers else if (err != ENOTFOUND || !create_new) 766332609Sasomers return (err); 7671592Srgrimes } 768207608Simp 769207608Simp /* 770207608Simp * This option is handled here because it (might) require(s) the 771207608Simp * size of the file. 772207608Simp */ 773207608Simp option_tsize(peer, NULL, mode, &stbuf); 774207608Simp 775129680Smdodd if (mode == RRQ) 776129680Smdodd fd = open(filename, O_RDONLY); 777129680Smdodd else { 778173852Sedwin if (create_new) { 779173852Sedwin if (increase_name) { 780173852Sedwin error = find_next_name(filename, &fd); 781173852Sedwin if (error > 0) 782173852Sedwin return (error + 100); 783173852Sedwin } else 784173852Sedwin fd = open(filename, 785173852Sedwin O_WRONLY | O_TRUNC | O_CREAT, 786173852Sedwin S_IRUSR | S_IWUSR | S_IRGRP | 787173852Sedwin S_IWGRP | S_IROTH | S_IWOTH ); 788173852Sedwin } else 789173852Sedwin fd = open(filename, O_WRONLY | O_TRUNC); 790129680Smdodd } 7911592Srgrimes if (fd < 0) 7921592Srgrimes return (errno + 100); 7931592Srgrimes file = fdopen(fd, (mode == RRQ)? "r":"w"); 7941592Srgrimes if (file == NULL) { 795129683Smdodd close(fd); 796129683Smdodd return (errno + 100); 7971592Srgrimes } 7981592Srgrimes return (0); 7991592Srgrimes} 8001592Srgrimes 801207608Simpstatic void 802207608Simptftp_xmitfile(int peer, const char *mode) 8031592Srgrimes{ 804207608Simp uint16_t block; 805207608Simp time_t now; 806207608Simp struct tftp_stats ts; 8071592Srgrimes 808339051Sasomers memset(&ts, 0, sizeof(ts)); 809207608Simp now = time(NULL); 810207608Simp if (debug&DEBUG_SIMPLE) 811207608Simp tftp_log(LOG_DEBUG, "Transmitting file"); 8121592Srgrimes 813207608Simp read_init(0, file, mode); 8141592Srgrimes block = 1; 815207608Simp tftp_send(peer, &block, &ts); 816207608Simp read_close(); 817207608Simp if (debug&DEBUG_SIMPLE) 818246139Smarius tftp_log(LOG_INFO, "Sent %jd bytes in %jd seconds", 819246139Smarius (intmax_t)ts.amount, (intmax_t)time(NULL) - now); 8201592Srgrimes} 8211592Srgrimes 822207608Simpstatic void 823207608Simptftp_recvfile(int peer, const char *mode) 8241592Srgrimes{ 825207608Simp uint16_t block; 826207608Simp struct timeval now1, now2; 827207608Simp struct tftp_stats ts; 8281592Srgrimes 829207608Simp gettimeofday(&now1, NULL); 830207608Simp if (debug&DEBUG_SIMPLE) 831207608Simp tftp_log(LOG_DEBUG, "Receiving file"); 8321592Srgrimes 833207608Simp write_init(0, file, mode); 8341592Srgrimes 8351592Srgrimes block = 0; 836207608Simp tftp_receive(peer, &block, &ts, NULL, 0); 8371592Srgrimes 838213099Smarius gettimeofday(&now2, NULL); 8391592Srgrimes 840207608Simp if (debug&DEBUG_SIMPLE) { 841207608Simp double f; 842207608Simp if (now1.tv_usec > now2.tv_usec) { 843207608Simp now2.tv_usec += 1000000; 844207608Simp now2.tv_sec--; 845207608Simp } 8461592Srgrimes 847207608Simp f = now2.tv_sec - now1.tv_sec + 848207608Simp (now2.tv_usec - now1.tv_usec) / 100000.0; 849207608Simp tftp_log(LOG_INFO, 850246139Smarius "Download of %jd bytes in %d blocks completed after %0.1f seconds\n", 851246139Smarius (intmax_t)ts.amount, block, f); 8521592Srgrimes } 85384047Sobrien 854207608Simp return; 85594443Sume} 856