tftpd.c revision 207608
11592Srgrimes/* 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. 131592Srgrimes * 3. All advertising materials mentioning features or use of this software 141592Srgrimes * must display the following acknowledgement: 151592Srgrimes * This product includes software developed by the University of 161592Srgrimes * California, Berkeley and its contributors. 171592Srgrimes * 4. Neither the name of the University nor the names of its contributors 181592Srgrimes * may be used to endorse or promote products derived from this software 191592Srgrimes * without specific prior written permission. 201592Srgrimes * 211592Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 221592Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 231592Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 241592Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 251592Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 261592Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 271592Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 281592Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 291592Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 301592Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 311592Srgrimes * SUCH DAMAGE. 321592Srgrimes */ 331592Srgrimes 341592Srgrimes#ifndef lint 3531512Scharnierstatic const char copyright[] = 361592Srgrimes"@(#) Copyright (c) 1983, 1993\n\ 371592Srgrimes The Regents of the University of California. All rights reserved.\n"; 381592Srgrimes#endif /* not lint */ 391592Srgrimes 401592Srgrimes#ifndef lint 4131512Scharnier#if 0 421592Srgrimesstatic char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93"; 4331512Scharnier#endif 441592Srgrimes#endif /* not lint */ 45207608Simp#include <sys/cdefs.h> 46207608Simp__FBSDID("$FreeBSD: head/libexec/tftpd/tftpd.c 207608 2010-05-04 06:19:19Z imp $"); 471592Srgrimes 481592Srgrimes/* 491592Srgrimes * Trivial file transfer protocol server. 501592Srgrimes * 511592Srgrimes * This version includes many modifications by Jim Guyton 521592Srgrimes * <guyton@rand-unix>. 531592Srgrimes */ 541592Srgrimes 551592Srgrimes#include <sys/param.h> 561592Srgrimes#include <sys/ioctl.h> 571592Srgrimes#include <sys/stat.h> 581592Srgrimes#include <sys/socket.h> 591592Srgrimes 601592Srgrimes#include <netinet/in.h> 611592Srgrimes#include <arpa/tftp.h> 621592Srgrimes 631592Srgrimes#include <ctype.h> 641592Srgrimes#include <errno.h> 651592Srgrimes#include <fcntl.h> 661592Srgrimes#include <netdb.h> 6731512Scharnier#include <pwd.h> 681592Srgrimes#include <stdio.h> 691592Srgrimes#include <stdlib.h> 701592Srgrimes#include <string.h> 711592Srgrimes#include <syslog.h> 72207608Simp#include <tcpd.h> 731592Srgrimes#include <unistd.h> 741592Srgrimes 75207608Simp#include "tftp-file.h" 76207608Simp#include "tftp-io.h" 77207608Simp#include "tftp-utils.h" 78207608Simp#include "tftp-transfer.h" 79207608Simp#include "tftp-options.h" 801592Srgrimes 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; 100173852Sedwinstatic 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 110207608SimpFILE *file; 111112452Sdwmalone 112207608Simpstruct 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 28718458Simp /* 288207608Simp * See if the client is allowed to talk to me. 289207608Simp * (This needs to be done before the chroot()) 290207608Simp */ 291207608Simp { 292207608Simp struct request_info req; 293207608Simp 294207608Simp request_init(&req, RQ_CLIENT_ADDR, peername, 0); 295207608Simp request_set(&req, RQ_DAEMON, "tftpd", 0); 296207608Simp 297207608Simp if (hosts_access(&req) == 0) { 298207608Simp if (debug&DEBUG_ACCESS) 299207608Simp tftp_log(LOG_WARNING, 300207608Simp "Access denied by 'tftpd' entry " 301207608Simp "in /etc/hosts.allow"); 302207608Simp 303207608Simp /* 304207608Simp * Full access might be disabled, but maybe the 305207608Simp * client is allowed to do read-only access. 306207608Simp */ 307207608Simp request_set(&req, RQ_DAEMON, "tftpd-ro", 0); 308207608Simp allow_ro = hosts_access(&req); 309207608Simp 310207608Simp request_set(&req, RQ_DAEMON, "tftpd-wo", 0); 311207608Simp allow_wo = hosts_access(&req); 312207608Simp 313207608Simp if (allow_ro == 0 && allow_wo == 0) { 314207608Simp tftp_log(LOG_WARNING, 315207608Simp "Unauthorized access from %s", peername); 316207608Simp exit(1); 317207608Simp } 318207608Simp 319207608Simp if (debug&DEBUG_ACCESS) { 320207608Simp if (allow_ro) 321207608Simp tftp_log(LOG_WARNING, 322207608Simp "But allowed readonly access " 323207608Simp "via 'tftpd-ro' entry"); 324207608Simp if (allow_wo) 325207608Simp tftp_log(LOG_WARNING, 326207608Simp "But allowed writeonly access " 327207608Simp "via 'tftpd-wo' entry"); 328207608Simp } 329207608Simp } else 330207608Simp if (debug&DEBUG_ACCESS) 331207608Simp tftp_log(LOG_WARNING, 332207608Simp "Full access allowed" 333207608Simp "in /etc/hosts.allow"); 334207608Simp } 335207608Simp 336207608Simp /* 33718458Simp * Since we exit here, we should do that only after the above 33818458Simp * recvfrom to keep inetd from constantly forking should there 33918458Simp * be a problem. See the above comment about system clogging. 34018458Simp */ 34118458Simp if (chroot_dir) { 342113714Sbillf if (ipchroot > 0) { 34371616Sbillf char *tempchroot; 34471616Sbillf struct stat sb; 34571616Sbillf int statret; 34694443Sume struct sockaddr_storage ss; 34794443Sume char hbuf[NI_MAXHOST]; 34871616Sbillf 349207608Simp statret = -1; 350207608Simp memcpy(&ss, &peer_sock, peer_sock.ss_len); 35194443Sume unmappedaddr((struct sockaddr_in6 *)&ss); 35294443Sume getnameinfo((struct sockaddr *)&ss, ss.ss_len, 35394443Sume hbuf, sizeof(hbuf), NULL, 0, 354146187Sume NI_NUMERICHOST); 35594443Sume asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); 356113714Sbillf if (ipchroot == 2) 357113714Sbillf statret = stat(tempchroot, &sb); 358113714Sbillf if (ipchroot == 1 || 359113714Sbillf (statret == 0 && (sb.st_mode & S_IFDIR))) 36071616Sbillf chroot_dir = tempchroot; 36171616Sbillf } 36218458Simp /* Must get this before chroot because /etc might go away */ 36365850Swollman if ((nobody = getpwnam(chuser)) == NULL) { 364207608Simp tftp_log(LOG_ERR, "%s: no such user", chuser); 36518458Simp exit(1); 36618458Simp } 36718458Simp if (chroot(chroot_dir)) { 368207608Simp tftp_log(LOG_ERR, "chroot: %s: %s", 369207608Simp chroot_dir, strerror(errno)); 37018458Simp exit(1); 37118458Simp } 372131358Scsjp chdir("/"); 373131358Scsjp setgroups(1, &nobody->pw_gid); 37418458Simp setuid(nobody->pw_uid); 37518458Simp } 37618458Simp 377207608Simp len = sizeof(me_sock); 378207608Simp if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) { 379207608Simp switch (me_sock.ss_family) { 38094443Sume case AF_INET: 381207608Simp ((struct sockaddr_in *)&me_sock)->sin_port = 0; 38294443Sume break; 38394443Sume case AF_INET6: 384207608Simp ((struct sockaddr_in6 *)&me_sock)->sin6_port = 0; 38594443Sume break; 38694443Sume default: 38794443Sume /* unsupported */ 38894443Sume break; 38994443Sume } 39094443Sume } else { 391207608Simp memset(&me_sock, 0, sizeof(me_sock)); 392207608Simp me_sock.ss_family = peer_sock.ss_family; 393207608Simp me_sock.ss_len = peer_sock.ss_len; 39494443Sume } 3951592Srgrimes close(0); 3961592Srgrimes close(1); 397207608Simp peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0); 3981592Srgrimes if (peer < 0) { 399207608Simp tftp_log(LOG_ERR, "socket: %s", strerror(errno)); 4001592Srgrimes exit(1); 4011592Srgrimes } 402207608Simp if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) { 403207608Simp tftp_log(LOG_ERR, "bind: %s", strerror(errno)); 4041592Srgrimes exit(1); 4051592Srgrimes } 406207608Simp 407207608Simp tp = (struct tftphdr *)recvbuffer; 408207608Simp tp->th_opcode = ntohs(tp->th_opcode); 409207608Simp if (tp->th_opcode == RRQ) { 410207608Simp if (allow_ro) 411207608Simp tftp_rrq(peer, tp->th_stuff, n - 1); 412207608Simp else { 413207608Simp tftp_log(LOG_WARNING, 414207608Simp "%s read access denied", peername); 415207608Simp exit(1); 416207608Simp } 4171592Srgrimes } 418207608Simp if (tp->th_opcode == WRQ) { 419207608Simp if (allow_wo) 420207608Simp tftp_wrq(peer, tp->th_stuff, n - 1); 421207608Simp else { 422207608Simp tftp_log(LOG_WARNING, 423207608Simp "%s write access denied", peername); 424207608Simp exit(1); 425207608Simp } 426207608Simp } 4271592Srgrimes exit(1); 4281592Srgrimes} 4291592Srgrimes 430130834Sbrianstatic void 431130834Sbrianreduce_path(char *fn) 432130834Sbrian{ 433130834Sbrian char *slash, *ptr; 434130834Sbrian 435130834Sbrian /* Reduce all "/+./" to "/" (just in case we've got "/./../" later */ 436130834Sbrian while ((slash = strstr(fn, "/./")) != NULL) { 437130834Sbrian for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 438130834Sbrian ; 439130834Sbrian slash += 2; 440130834Sbrian while (*slash) 441130834Sbrian *++ptr = *++slash; 442130834Sbrian } 443130834Sbrian 444130834Sbrian /* Now reduce all "/something/+../" to "/" */ 445130834Sbrian while ((slash = strstr(fn, "/../")) != NULL) { 446130834Sbrian if (slash == fn) 447130834Sbrian break; 448130834Sbrian for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 449130834Sbrian ; 450130834Sbrian for (ptr--; ptr >= fn; ptr--) 451130834Sbrian if (*ptr == '/') 452130834Sbrian break; 453130834Sbrian if (ptr < fn) 454130834Sbrian break; 455130834Sbrian slash += 3; 456130834Sbrian while (*slash) 457130834Sbrian *++ptr = *++slash; 458130834Sbrian } 459130834Sbrian} 460130834Sbrian 461207608Simpstatic char * 462207608Simpparse_header(int peer, char *recvbuffer, ssize_t size, 463207608Simp char **filename, char **mode) 464207608Simp{ 465207608Simp char *cp; 466207608Simp int i; 467207608Simp struct formats *pf; 4681592Srgrimes 469207608Simp *mode = NULL; 470207608Simp cp = recvbuffer; 4711592Srgrimes 472207608Simp i = get_field(peer, recvbuffer, size); 473207608Simp if (i >= PATH_MAX) { 474207608Simp tftp_log(LOG_ERR, "Bad option - filename too long"); 475207608Simp send_error(peer, EBADOP); 476207608Simp exit(1); 477207608Simp } 478207608Simp *filename = recvbuffer; 479207608Simp tftp_log(LOG_INFO, "Filename: '%s'", *filename); 480207608Simp cp += i; 48184047Sobrien 482207608Simp i = get_field(peer, cp, size); 483207608Simp *mode = cp; 484207608Simp cp += i; 48584047Sobrien 486207608Simp /* Find the file transfer mode */ 487207608Simp for (cp = *mode; *cp; cp++) 488207608Simp if (isupper(*cp)) 489207608Simp *cp = tolower(*cp); 490207608Simp for (pf = formats; pf->f_mode; pf++) 491207608Simp if (strcmp(pf->f_mode, *mode) == 0) 492207608Simp break; 493207608Simp if (pf->f_mode == NULL) { 494207608Simp tftp_log(LOG_ERR, 495207608Simp "Bad option - Unknown transfer mode (%s)", *mode); 496207608Simp send_error(peer, EBADOP); 497207608Simp exit(1); 498207608Simp } 499207608Simp tftp_log(LOG_INFO, "Mode: '%s'", *mode); 500207608Simp 501207608Simp return (cp + 1); 502207608Simp} 503207608Simp 5041592Srgrimes/* 505207608Simp * WRQ - receive a file from the client 5061592Srgrimes */ 5071592Srgrimesvoid 508207608Simptftp_wrq(int peer, char *recvbuffer, ssize_t size) 5091592Srgrimes{ 51090333Simp char *cp; 511207608Simp int has_options = 0, ecode; 512207608Simp char *filename, *mode; 513141922Sstefanf char fnbuf[PATH_MAX]; 5141592Srgrimes 515207608Simp cp = parse_header(peer, recvbuffer, size, &filename, &mode); 516207608Simp size -= (cp - recvbuffer) + 1; 517207608Simp 518207608Simp strcpy(fnbuf, filename); 519207608Simp reduce_path(fnbuf); 520207608Simp filename = fnbuf; 521207608Simp 522207608Simp if (size > 0) { 523207608Simp if (options_rfc_enabled) 524207608Simp has_options = !parse_options(peer, cp, size); 525207608Simp else 526207608Simp tftp_log(LOG_INFO, "Options found but not enabled"); 5271592Srgrimes } 528207608Simp 529207608Simp ecode = validate_access(peer, &filename, WRQ); 530207608Simp if (ecode == 0) { 531207608Simp if (has_options) 532207608Simp send_oack(peer); 533207608Simp else 534207608Simp send_ack(peer, 0); 5351592Srgrimes } 536207608Simp if (logging) { 537207608Simp tftp_log(LOG_INFO, "%s: write request for %s: %s", peername, 538207608Simp filename, errtomsg(ecode)); 539122916Ssobomax } 540207608Simp 541207608Simp tftp_recvfile(peer, mode); 542207608Simp exit(0); 543207608Simp} 544207608Simp 545207608Simp/* 546207608Simp * RRQ - send a file to the client 547207608Simp */ 548207608Simpvoid 549207608Simptftp_rrq(int peer, char *recvbuffer, ssize_t size) 550207608Simp{ 551207608Simp char *cp; 552207608Simp int has_options = 0, ecode; 553207608Simp char *filename, *mode; 554207608Simp char fnbuf[PATH_MAX]; 555207608Simp 556207608Simp cp = parse_header(peer, recvbuffer, size, &filename, &mode); 557207608Simp size -= (cp - recvbuffer) + 1; 558207608Simp 559207608Simp strcpy(fnbuf, filename); 560130834Sbrian reduce_path(fnbuf); 561122916Ssobomax filename = fnbuf; 562207608Simp 563207608Simp if (size > 0) { 564207608Simp if (options_rfc_enabled) 565207608Simp has_options = !parse_options(peer, cp, size); 566207608Simp else 567207608Simp tftp_log(LOG_INFO, "Options found but not enabled"); 5681592Srgrimes } 569207608Simp 570207608Simp ecode = validate_access(peer, &filename, RRQ); 571207608Simp if (ecode == 0) { 572207608Simp if (has_options) { 573207608Simp int n; 574207608Simp char lrecvbuffer[MAXPKTSIZE]; 575207608Simp struct tftphdr *rp = (struct tftphdr *)lrecvbuffer; 576207608Simp 577207608Simp send_oack(peer); 578207608Simp n = receive_packet(peer, lrecvbuffer, MAXPKTSIZE, 579207608Simp NULL, timeoutpacket); 580207608Simp if (n < 0) { 581207608Simp if (debug&DEBUG_SIMPLE) 582207608Simp tftp_log(LOG_DEBUG, "Aborting: %s", 583207608Simp rp_strerror(n)); 584207608Simp return; 58584047Sobrien } 586207608Simp if (rp->th_opcode != ACK) { 587207608Simp if (debug&DEBUG_SIMPLE) 588207608Simp tftp_log(LOG_DEBUG, 589207608Simp "Expected ACK, got %s on OACK", 590207608Simp packettype(rp->th_opcode)); 591207608Simp return; 592207608Simp } 59384047Sobrien } 59484047Sobrien } 59584047Sobrien 596207608Simp if (logging) 597207608Simp tftp_log(LOG_INFO, "%s: read request for %s: %s", peername, 598207608Simp filename, errtomsg(ecode)); 59945393Sbrian 6001592Srgrimes if (ecode) { 6011592Srgrimes /* 6021592Srgrimes * Avoid storms of naks to a RRQ broadcast for a relative 6031592Srgrimes * bootfile pathname from a diskless Sun. 6041592Srgrimes */ 6051592Srgrimes if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 6061592Srgrimes exit(0); 607207608Simp tftp_log(LOG_ERR, "Prevent NAK storm"); 608207608Simp send_error(peer, ecode); 6091592Srgrimes exit(1); 6101592Srgrimes } 611207608Simp tftp_xmitfile(peer, mode); 6121592Srgrimes} 6131592Srgrimes 6141592Srgrimes/* 615173852Sedwin * Find the next value for YYYYMMDD.nn when the file to be written should 616173852Sedwin * be unique. Due to the limitations of nn, we will fail if nn reaches 100. 617173852Sedwin * Besides, that is four updates per hour on a file, which is kind of 618173852Sedwin * execessive anyway. 619173852Sedwin */ 620173852Sedwinstatic int 621173852Sedwinfind_next_name(char *filename, int *fd) 622173852Sedwin{ 623173852Sedwin int i; 624173852Sedwin time_t tval; 625173852Sedwin size_t len; 626173852Sedwin struct tm lt; 627173852Sedwin char yyyymmdd[MAXPATHLEN]; 628173852Sedwin char newname[MAXPATHLEN]; 629173852Sedwin 630173852Sedwin /* Create the YYYYMMDD part of the filename */ 631173852Sedwin time(&tval); 632173852Sedwin lt = *localtime(&tval); 633173852Sedwin len = strftime(yyyymmdd, sizeof(yyyymmdd), newfile_format, <); 634173852Sedwin if (len == 0) { 635173852Sedwin syslog(LOG_WARNING, 636173852Sedwin "Filename suffix too long (%d characters maximum)", 637173852Sedwin MAXPATHLEN); 638173852Sedwin return (EACCESS); 639173852Sedwin } 640173852Sedwin 641173852Sedwin /* Make sure the new filename is not too long */ 642173852Sedwin if (strlen(filename) > MAXPATHLEN - len - 5) { 643173852Sedwin syslog(LOG_WARNING, 644207608Simp "Filename too long (%zd characters, %zd maximum)", 645173852Sedwin strlen(filename), MAXPATHLEN - len - 5); 646173852Sedwin return (EACCESS); 647173852Sedwin } 648173852Sedwin 649173852Sedwin /* Find the first file which doesn't exist */ 650173852Sedwin for (i = 0; i < 100; i++) { 651173852Sedwin sprintf(newname, "%s.%s.%02d", filename, yyyymmdd, i); 652173852Sedwin *fd = open(newname, 653173852Sedwin O_WRONLY | O_CREAT | O_EXCL, 654173852Sedwin S_IRUSR | S_IWUSR | S_IRGRP | 655173852Sedwin S_IWGRP | S_IROTH | S_IWOTH); 656173852Sedwin if (*fd > 0) 657173852Sedwin return 0; 658173852Sedwin } 659173852Sedwin 660173852Sedwin return (EEXIST); 661173852Sedwin} 662173852Sedwin 663173852Sedwin/* 6641592Srgrimes * Validate file access. Since we 6651592Srgrimes * have no uid or gid, for now require 6661592Srgrimes * file to exist and be publicly 6671592Srgrimes * readable/writable. 6681592Srgrimes * If we were invoked with arguments 6691592Srgrimes * from inetd then the file must also be 6701592Srgrimes * in one of the given directory prefixes. 6711592Srgrimes * Note also, full path name must be 6721592Srgrimes * given as we have no login directory. 6731592Srgrimes */ 6741592Srgrimesint 675207608Simpvalidate_access(int peer, char **filep, int mode) 6761592Srgrimes{ 6771592Srgrimes struct stat stbuf; 6781592Srgrimes int fd; 679173852Sedwin int error; 6801592Srgrimes struct dirlist *dirp; 6811592Srgrimes static char pathname[MAXPATHLEN]; 6821592Srgrimes char *filename = *filep; 6831592Srgrimes 6841592Srgrimes /* 6851592Srgrimes * Prevent tricksters from getting around the directory restrictions 6861592Srgrimes */ 6871592Srgrimes if (strstr(filename, "/../")) 6881592Srgrimes return (EACCESS); 6891592Srgrimes 6901592Srgrimes if (*filename == '/') { 6911592Srgrimes /* 6921592Srgrimes * Allow the request if it's in one of the approved locations. 6931592Srgrimes * Special case: check the null prefix ("/") by looking 6941592Srgrimes * for length = 1 and relying on the arg. processing that 6951592Srgrimes * it's a /. 6961592Srgrimes */ 6971592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 6981592Srgrimes if (dirp->len == 1 || 6991592Srgrimes (!strncmp(filename, dirp->name, dirp->len) && 7001592Srgrimes filename[dirp->len] == '/')) 7011592Srgrimes break; 7021592Srgrimes } 7031592Srgrimes /* If directory list is empty, allow access to any file */ 7041592Srgrimes if (dirp->name == NULL && dirp != dirs) 7051592Srgrimes return (EACCESS); 7061592Srgrimes if (stat(filename, &stbuf) < 0) 7071592Srgrimes return (errno == ENOENT ? ENOTFOUND : EACCESS); 7081592Srgrimes if ((stbuf.st_mode & S_IFMT) != S_IFREG) 7091592Srgrimes return (ENOTFOUND); 7101592Srgrimes if (mode == RRQ) { 7111592Srgrimes if ((stbuf.st_mode & S_IROTH) == 0) 7121592Srgrimes return (EACCESS); 7131592Srgrimes } else { 7141592Srgrimes if ((stbuf.st_mode & S_IWOTH) == 0) 7151592Srgrimes return (EACCESS); 7161592Srgrimes } 7171592Srgrimes } else { 7181592Srgrimes int err; 7191592Srgrimes 7201592Srgrimes /* 7211592Srgrimes * Relative file name: search the approved locations for it. 7226750Sjkh * Don't allow write requests that avoid directory 7231592Srgrimes * restrictions. 7241592Srgrimes */ 7251592Srgrimes 7266750Sjkh if (!strncmp(filename, "../", 3)) 7271592Srgrimes return (EACCESS); 7281592Srgrimes 7291592Srgrimes /* 7301592Srgrimes * If the file exists in one of the directories and isn't 7311592Srgrimes * readable, continue looking. However, change the error code 7321592Srgrimes * to give an indication that the file exists. 7331592Srgrimes */ 7341592Srgrimes err = ENOTFOUND; 7351592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 73624193Simp snprintf(pathname, sizeof(pathname), "%s/%s", 73724193Simp dirp->name, filename); 7381592Srgrimes if (stat(pathname, &stbuf) == 0 && 7391592Srgrimes (stbuf.st_mode & S_IFMT) == S_IFREG) { 7401592Srgrimes if ((stbuf.st_mode & S_IROTH) != 0) { 7411592Srgrimes break; 7421592Srgrimes } 7431592Srgrimes err = EACCESS; 7441592Srgrimes } 7451592Srgrimes } 746129680Smdodd if (dirp->name != NULL) 747129680Smdodd *filep = filename = pathname; 748129680Smdodd else if (mode == RRQ) 7491592Srgrimes return (err); 7501592Srgrimes } 751207608Simp 752207608Simp /* 753207608Simp * This option is handled here because it (might) require(s) the 754207608Simp * size of the file. 755207608Simp */ 756207608Simp option_tsize(peer, NULL, mode, &stbuf); 757207608Simp 758129680Smdodd if (mode == RRQ) 759129680Smdodd fd = open(filename, O_RDONLY); 760129680Smdodd else { 761173852Sedwin if (create_new) { 762173852Sedwin if (increase_name) { 763173852Sedwin error = find_next_name(filename, &fd); 764173852Sedwin if (error > 0) 765173852Sedwin return (error + 100); 766173852Sedwin } else 767173852Sedwin fd = open(filename, 768173852Sedwin O_WRONLY | O_TRUNC | O_CREAT, 769173852Sedwin S_IRUSR | S_IWUSR | S_IRGRP | 770173852Sedwin S_IWGRP | S_IROTH | S_IWOTH ); 771173852Sedwin } else 772173852Sedwin fd = open(filename, O_WRONLY | O_TRUNC); 773129680Smdodd } 7741592Srgrimes if (fd < 0) 7751592Srgrimes return (errno + 100); 7761592Srgrimes file = fdopen(fd, (mode == RRQ)? "r":"w"); 7771592Srgrimes if (file == NULL) { 778129683Smdodd close(fd); 779129683Smdodd return (errno + 100); 7801592Srgrimes } 7811592Srgrimes return (0); 7821592Srgrimes} 7831592Srgrimes 784207608Simpstatic void 785207608Simptftp_xmitfile(int peer, const char *mode) 7861592Srgrimes{ 787207608Simp uint16_t block; 788207608Simp uint32_t amount; 789207608Simp time_t now; 790207608Simp struct tftp_stats ts; 7911592Srgrimes 792207608Simp now = time(NULL); 793207608Simp if (debug&DEBUG_SIMPLE) 794207608Simp tftp_log(LOG_DEBUG, "Transmitting file"); 7951592Srgrimes 796207608Simp read_init(0, file, mode); 7971592Srgrimes block = 1; 798207608Simp tftp_send(peer, &block, &ts); 799207608Simp read_close(); 800207608Simp if (debug&DEBUG_SIMPLE) 801207608Simp tftp_log(LOG_INFO, "Sent %d bytes in %d seconds", 802207608Simp amount, time(NULL) - now); 8031592Srgrimes} 8041592Srgrimes 805207608Simpstatic void 806207608Simptftp_recvfile(int peer, const char *mode) 8071592Srgrimes{ 808207608Simp uint32_t filesize; 809207608Simp uint16_t block; 810207608Simp struct timeval now1, now2; 811207608Simp struct tftp_stats ts; 8121592Srgrimes 813207608Simp gettimeofday(&now1, NULL); 814207608Simp if (debug&DEBUG_SIMPLE) 815207608Simp tftp_log(LOG_DEBUG, "Receiving file"); 8161592Srgrimes 817207608Simp write_init(0, file, mode); 8181592Srgrimes 8191592Srgrimes block = 0; 820207608Simp tftp_receive(peer, &block, &ts, NULL, 0); 8211592Srgrimes 822207608Simp write_close(); 8231592Srgrimes 824207608Simp if (debug&DEBUG_SIMPLE) { 825207608Simp double f; 826207608Simp if (now1.tv_usec > now2.tv_usec) { 827207608Simp now2.tv_usec += 1000000; 828207608Simp now2.tv_sec--; 829207608Simp } 8301592Srgrimes 831207608Simp f = now2.tv_sec - now1.tv_sec + 832207608Simp (now2.tv_usec - now1.tv_usec) / 100000.0; 833207608Simp tftp_log(LOG_INFO, 834207608Simp "Download of %d bytes in %d blocks completed after %0.1f seconds\n", 835207608Simp filesize, block, f); 8361592Srgrimes } 83784047Sobrien 838207608Simp return; 83994443Sume} 84094443Sume 841