tftpd.c revision 241848
1239268Sgonzo/* 2244469Scognet * Copyright (c) 1983, 1993 3239268Sgonzo * The Regents of the University of California. All rights reserved. 4239268Sgonzo * 5239268Sgonzo * Redistribution and use in source and binary forms, with or without 6239268Sgonzo * modification, are permitted provided that the following conditions 7239268Sgonzo * are met: 8239268Sgonzo * 1. Redistributions of source code must retain the above copyright 9239268Sgonzo * notice, this list of conditions and the following disclaimer. 10239268Sgonzo * 2. Redistributions in binary form must reproduce the above copyright 11239268Sgonzo * notice, this list of conditions and the following disclaimer in the 12239268Sgonzo * documentation and/or other materials provided with the distribution. 13239268Sgonzo * 3. All advertising materials mentioning features or use of this software 14239268Sgonzo * must display the following acknowledgement: 15239268Sgonzo * This product includes software developed by the University of 16239268Sgonzo * California, Berkeley and its contributors. 17239268Sgonzo * 4. Neither the name of the University nor the names of its contributors 18239268Sgonzo * may be used to endorse or promote products derived from this software 19239268Sgonzo * without specific prior written permission. 20239268Sgonzo * 21239268Sgonzo * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22239268Sgonzo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23239268Sgonzo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24239268Sgonzo * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25239268Sgonzo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26239268Sgonzo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27239268Sgonzo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28239268Sgonzo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29239268Sgonzo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30239268Sgonzo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31239268Sgonzo * SUCH DAMAGE. 32239268Sgonzo */ 33239268Sgonzo 34239268Sgonzo#ifndef lint 35239268Sgonzostatic const char copyright[] = 36239268Sgonzo"@(#) Copyright (c) 1983, 1993\n\ 37239268Sgonzo The Regents of the University of California. All rights reserved.\n"; 38239268Sgonzo#endif /* not lint */ 39239268Sgonzo 40239268Sgonzo#ifndef lint 41239268Sgonzo#if 0 42239268Sgonzostatic char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93"; 43239268Sgonzo#endif 44244469Scognet#endif /* not lint */ 45239268Sgonzo#include <sys/cdefs.h> 46239268Sgonzo__FBSDID("$FreeBSD: head/libexec/tftpd/tftpd.c 241848 2012-10-22 03:07:05Z eadler $"); 47239268Sgonzo 48239268Sgonzo/* 49239268Sgonzo * Trivial file transfer protocol server. 50239268Sgonzo * 51239268Sgonzo * This version includes many modifications by Jim Guyton 52239268Sgonzo * <guyton@rand-unix>. 53239268Sgonzo */ 54239268Sgonzo 55239268Sgonzo#include <sys/param.h> 56239268Sgonzo#include <sys/ioctl.h> 57239268Sgonzo#include <sys/stat.h> 58244469Scognet#include <sys/socket.h> 59244469Scognet 60239268Sgonzo#include <netinet/in.h> 61239268Sgonzo#include <arpa/tftp.h> 62239268Sgonzo 63239268Sgonzo#include <ctype.h> 64239268Sgonzo#include <errno.h> 65239268Sgonzo#include <fcntl.h> 66239268Sgonzo#include <netdb.h> 67239268Sgonzo#include <pwd.h> 68239268Sgonzo#include <stdio.h> 69239268Sgonzo#include <stdlib.h> 70239268Sgonzo#include <string.h> 71239268Sgonzo#include <syslog.h> 72239268Sgonzo#include <tcpd.h> 73239268Sgonzo#include <unistd.h> 74239268Sgonzo 75239268Sgonzo#include "tftp-file.h" 76239268Sgonzo#include "tftp-io.h" 77239268Sgonzo#include "tftp-utils.h" 78239268Sgonzo#include "tftp-transfer.h" 79239268Sgonzo#include "tftp-options.h" 80239268Sgonzo 81239268Sgonzostatic void tftp_wrq(int peer, char *, ssize_t); 82239268Sgonzostatic void tftp_rrq(int peer, char *, ssize_t); 83239268Sgonzo 84239268Sgonzo/* 85239268Sgonzo * Null-terminated directory prefix list for absolute pathname requests and 86239268Sgonzo * search list for relative pathname requests. 87239268Sgonzo * 88239268Sgonzo * MAXDIRS should be at least as large as the number of arguments that 89239268Sgonzo * inetd allows (currently 20). 90239268Sgonzo */ 91239268Sgonzo#define MAXDIRS 20 92239268Sgonzostatic struct dirlist { 93239268Sgonzo const char *name; 94239268Sgonzo int len; 95239268Sgonzo} dirs[MAXDIRS+1]; 96239268Sgonzostatic int suppress_naks; 97239268Sgonzostatic int logging; 98239268Sgonzostatic int ipchroot; 99244469Scognetstatic int create_new = 0; 100244469Scognetstatic const char *newfile_format = "%Y%m%d"; 101244469Scognetstatic int increase_name = 0; 102244469Scognetstatic mode_t mask = S_IWGRP | S_IWOTH; 103244469Scognet 104244469Scognetstruct formats; 105244469Scognetstatic void tftp_recvfile(int peer, const char *mode); 106239268Sgonzostatic void tftp_xmitfile(int peer, const char *mode); 107244469Scognetstatic int validate_access(int peer, char **, int); 108239268Sgonzostatic char peername[NI_MAXHOST]; 109239268Sgonzo 110239268Sgonzostatic FILE *file; 111239268Sgonzo 112239268Sgonzostatic struct formats { 113239268Sgonzo const char *f_mode; 114239268Sgonzo int f_convert; 115239268Sgonzo} formats[] = { 116239268Sgonzo { "netascii", 1 }, 117239268Sgonzo { "octet", 0 }, 118239268Sgonzo { NULL, 0 } 119239268Sgonzo}; 120239268Sgonzo 121239268Sgonzoint 122239268Sgonzomain(int argc, char *argv[]) 123239268Sgonzo{ 124239268Sgonzo struct tftphdr *tp; 125239268Sgonzo int peer; 126239268Sgonzo socklen_t peerlen, len; 127239268Sgonzo ssize_t n; 128239268Sgonzo int ch; 129239268Sgonzo char *chroot_dir = NULL; 130239268Sgonzo struct passwd *nobody; 131239268Sgonzo const char *chuser = "nobody"; 132239268Sgonzo char recvbuffer[MAXPKTSIZE]; 133239268Sgonzo int allow_ro = 1, allow_wo = 1; 134239268Sgonzo 135239268Sgonzo tzset(); /* syslog in localtime */ 136239268Sgonzo acting_as_client = 0; 137239268Sgonzo 138239268Sgonzo tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 139239268Sgonzo while ((ch = getopt(argc, argv, "cCd:F:lnoOp:s:u:U:wW")) != -1) { 140239268Sgonzo switch (ch) { 141239268Sgonzo case 'c': 142239268Sgonzo ipchroot = 1; 143239268Sgonzo break; 144239268Sgonzo case 'C': 145239268Sgonzo ipchroot = 2; 146239268Sgonzo break; 147239268Sgonzo case 'd': 148239268Sgonzo if (atoi(optarg) != 0) 149239268Sgonzo debug += atoi(optarg); 150239268Sgonzo else 151239268Sgonzo debug |= debug_finds(optarg); 152239268Sgonzo break; 153239268Sgonzo case 'F': 154239268Sgonzo newfile_format = optarg; 155239268Sgonzo break; 156239268Sgonzo case 'l': 157239268Sgonzo logging = 1; 158239268Sgonzo break; 159239268Sgonzo case 'n': 160239268Sgonzo suppress_naks = 1; 161239268Sgonzo break; 162239268Sgonzo case 'o': 163239268Sgonzo options_rfc_enabled = 0; 164244469Scognet break; 165244469Scognet case 'O': 166239268Sgonzo options_extra_enabled = 0; 167239268Sgonzo break; 168239268Sgonzo case 'p': 169239268Sgonzo packetdroppercentage = atoi(optarg); 170239268Sgonzo tftp_log(LOG_INFO, 171239268Sgonzo "Randomly dropping %d out of 100 packets", 172239268Sgonzo packetdroppercentage); 173239268Sgonzo break; 174239268Sgonzo case 's': 175239268Sgonzo chroot_dir = optarg; 176239268Sgonzo break; 177239268Sgonzo case 'u': 178239268Sgonzo chuser = optarg; 179239268Sgonzo break; 180239268Sgonzo case 'U': 181239268Sgonzo mask = strtol(optarg, NULL, 0); 182239268Sgonzo break; 183239268Sgonzo case 'w': 184239268Sgonzo create_new = 1; 185244469Scognet break; 186244469Scognet case 'W': 187244469Scognet create_new = 1; 188244469Scognet increase_name = 1; 189244469Scognet break; 190244469Scognet default: 191244469Scognet tftp_log(LOG_WARNING, 192244469Scognet "ignoring unknown option -%c", ch); 193244469Scognet } 194244469Scognet } 195244469Scognet if (optind < argc) { 196244469Scognet struct dirlist *dirp; 197244469Scognet 198244469Scognet /* Get list of directory prefixes. Skip relative pathnames. */ 199244469Scognet for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; 200244469Scognet optind++) { 201244469Scognet if (argv[optind][0] == '/') { 202244469Scognet dirp->name = argv[optind]; 203244469Scognet dirp->len = strlen(dirp->name); 204244469Scognet dirp++; 205244469Scognet } 206244469Scognet } 207244469Scognet } 208244469Scognet else if (chroot_dir) { 209244469Scognet dirs->name = "/"; 210244469Scognet dirs->len = 1; 211244469Scognet } 212244469Scognet if (ipchroot > 0 && chroot_dir == NULL) { 213244469Scognet tftp_log(LOG_ERR, "-c requires -s"); 214244469Scognet exit(1); 215244469Scognet } 216244469Scognet 217239268Sgonzo umask(mask); 218239268Sgonzo 219239268Sgonzo { 220239268Sgonzo int on = 1; 221239268Sgonzo if (ioctl(0, FIONBIO, &on) < 0) { 222239268Sgonzo tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno)); 223239268Sgonzo exit(1); 224239268Sgonzo } 225239268Sgonzo } 226239268Sgonzo 227239268Sgonzo /* Find out who we are talking to and what we are going to do */ 228239268Sgonzo peerlen = sizeof(peer_sock); 229239268Sgonzo n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, 230239268Sgonzo (struct sockaddr *)&peer_sock, &peerlen); 231239268Sgonzo if (n < 0) { 232239268Sgonzo tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno)); 233239268Sgonzo exit(1); 234239268Sgonzo } 235239268Sgonzo getnameinfo((struct sockaddr *)&peer_sock, peer_sock.ss_len, 236239268Sgonzo peername, sizeof(peername), NULL, 0, NI_NUMERICHOST); 237239268Sgonzo 238239268Sgonzo /* 239239268Sgonzo * Now that we have read the message out of the UDP 240239268Sgonzo * socket, we fork and exit. Thus, inetd will go back 241239268Sgonzo * to listening to the tftp port, and the next request 242239268Sgonzo * to come in will start up a new instance of tftpd. 243239268Sgonzo * 244239268Sgonzo * We do this so that inetd can run tftpd in "wait" mode. 245239268Sgonzo * The problem with tftpd running in "nowait" mode is that 246239268Sgonzo * inetd may get one or more successful "selects" on the 247239268Sgonzo * tftp port before we do our receive, so more than one 248239268Sgonzo * instance of tftpd may be started up. Worse, if tftpd 249239268Sgonzo * break before doing the above "recvfrom", inetd would 250239268Sgonzo * spawn endless instances, clogging the system. 251239268Sgonzo */ 252239268Sgonzo { 253239268Sgonzo int i, pid; 254239268Sgonzo 255239268Sgonzo for (i = 1; i < 20; i++) { 256239268Sgonzo pid = fork(); 257239268Sgonzo if (pid < 0) { 258239268Sgonzo sleep(i); 259239268Sgonzo /* 260239268Sgonzo * flush out to most recently sent request. 261239268Sgonzo * 262239268Sgonzo * This may drop some request, but those 263239268Sgonzo * will be resent by the clients when 264239268Sgonzo * they timeout. The positive effect of 265239268Sgonzo * this flush is to (try to) prevent more 266239268Sgonzo * than one tftpd being started up to service 267239268Sgonzo * a single request from a single client. 268239268Sgonzo */ 269239268Sgonzo peerlen = sizeof peer_sock; 270239268Sgonzo i = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, 271239268Sgonzo (struct sockaddr *)&peer_sock, &peerlen); 272239268Sgonzo if (i > 0) { 273239268Sgonzo n = i; 274239268Sgonzo } 275239268Sgonzo } else { 276239268Sgonzo break; 277239268Sgonzo } 278239268Sgonzo } 279239268Sgonzo if (pid < 0) { 280239268Sgonzo tftp_log(LOG_ERR, "fork: %s", strerror(errno)); 281239268Sgonzo exit(1); 282239268Sgonzo } else if (pid != 0) { 283239268Sgonzo exit(0); 284239268Sgonzo } 285239268Sgonzo } 286239268Sgonzo 287239268Sgonzo /* 288239268Sgonzo * See if the client is allowed to talk to me. 289239268Sgonzo * (This needs to be done before the chroot()) 290239268Sgonzo */ 291239268Sgonzo { 292239268Sgonzo struct request_info req; 293239268Sgonzo 294239268Sgonzo request_init(&req, RQ_CLIENT_ADDR, peername, 0); 295239268Sgonzo request_set(&req, RQ_DAEMON, "tftpd", 0); 296239268Sgonzo 297239268Sgonzo if (hosts_access(&req) == 0) { 298239268Sgonzo if (debug&DEBUG_ACCESS) 299239268Sgonzo tftp_log(LOG_WARNING, 300239268Sgonzo "Access denied by 'tftpd' entry " 301239268Sgonzo "in /etc/hosts.allow"); 302239268Sgonzo 303239268Sgonzo /* 304239268Sgonzo * Full access might be disabled, but maybe the 305239268Sgonzo * client is allowed to do read-only access. 306239268Sgonzo */ 307239268Sgonzo request_set(&req, RQ_DAEMON, "tftpd-ro", 0); 308239268Sgonzo allow_ro = hosts_access(&req); 309239268Sgonzo 310239268Sgonzo request_set(&req, RQ_DAEMON, "tftpd-wo", 0); 311239268Sgonzo allow_wo = hosts_access(&req); 312239268Sgonzo 313239268Sgonzo if (allow_ro == 0 && allow_wo == 0) { 314239268Sgonzo tftp_log(LOG_WARNING, 315239268Sgonzo "Unauthorized access from %s", peername); 316239268Sgonzo exit(1); 317239268Sgonzo } 318239268Sgonzo 319239268Sgonzo if (debug&DEBUG_ACCESS) { 320239268Sgonzo if (allow_ro) 321239268Sgonzo tftp_log(LOG_WARNING, 322239268Sgonzo "But allowed readonly access " 323239268Sgonzo "via 'tftpd-ro' entry"); 324239268Sgonzo if (allow_wo) 325239268Sgonzo tftp_log(LOG_WARNING, 326239268Sgonzo "But allowed writeonly access " 327239268Sgonzo "via 'tftpd-wo' entry"); 328239268Sgonzo } 329239268Sgonzo } else 330239268Sgonzo if (debug&DEBUG_ACCESS) 331239268Sgonzo tftp_log(LOG_WARNING, 332239268Sgonzo "Full access allowed" 333239268Sgonzo "in /etc/hosts.allow"); 334239268Sgonzo } 335239268Sgonzo 336239268Sgonzo /* 337239268Sgonzo * Since we exit here, we should do that only after the above 338239268Sgonzo * recvfrom to keep inetd from constantly forking should there 339239268Sgonzo * be a problem. See the above comment about system clogging. 340239268Sgonzo */ 341239268Sgonzo if (chroot_dir) { 342239268Sgonzo if (ipchroot > 0) { 343239268Sgonzo char *tempchroot; 344239268Sgonzo struct stat sb; 345239268Sgonzo int statret; 346239268Sgonzo struct sockaddr_storage ss; 347239268Sgonzo char hbuf[NI_MAXHOST]; 348239268Sgonzo 349239268Sgonzo statret = -1; 350239268Sgonzo memcpy(&ss, &peer_sock, peer_sock.ss_len); 351239268Sgonzo unmappedaddr((struct sockaddr_in6 *)&ss); 352239268Sgonzo getnameinfo((struct sockaddr *)&ss, ss.ss_len, 353239268Sgonzo hbuf, sizeof(hbuf), NULL, 0, 354239268Sgonzo NI_NUMERICHOST); 355239268Sgonzo asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); 356239268Sgonzo if (ipchroot == 2) 357239268Sgonzo statret = stat(tempchroot, &sb); 358239268Sgonzo if (ipchroot == 1 || 359239268Sgonzo (statret == 0 && (sb.st_mode & S_IFDIR))) 360239268Sgonzo chroot_dir = tempchroot; 361239268Sgonzo } 362239268Sgonzo /* Must get this before chroot because /etc might go away */ 363239268Sgonzo if ((nobody = getpwnam(chuser)) == NULL) { 364239268Sgonzo tftp_log(LOG_ERR, "%s: no such user", chuser); 365239268Sgonzo exit(1); 366239268Sgonzo } 367239268Sgonzo if (chroot(chroot_dir)) { 368239268Sgonzo tftp_log(LOG_ERR, "chroot: %s: %s", 369239268Sgonzo chroot_dir, strerror(errno)); 370244469Scognet exit(1); 371244469Scognet } 372244469Scognet chdir("/"); 373244469Scognet setgroups(1, &nobody->pw_gid); 374244469Scognet if (setuid(nobody->pw_uid) != 0) { 375244469Scognet tftp_log(LOG_ERR, "setuid failed"); 376244469Scognet exit(1); 377244469Scognet } 378244469Scognet } 379244469Scognet 380244469Scognet len = sizeof(me_sock); 381244469Scognet if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) { 382239268Sgonzo switch (me_sock.ss_family) { 383239268Sgonzo case AF_INET: 384239268Sgonzo ((struct sockaddr_in *)&me_sock)->sin_port = 0; 385239268Sgonzo break; 386239268Sgonzo case AF_INET6: 387239268Sgonzo ((struct sockaddr_in6 *)&me_sock)->sin6_port = 0; 388239268Sgonzo break; 389239268Sgonzo default: 390239268Sgonzo /* unsupported */ 391239268Sgonzo break; 392239268Sgonzo } 393239268Sgonzo } else { 394239268Sgonzo memset(&me_sock, 0, sizeof(me_sock)); 395239268Sgonzo me_sock.ss_family = peer_sock.ss_family; 396239268Sgonzo me_sock.ss_len = peer_sock.ss_len; 397239268Sgonzo } 398239268Sgonzo close(0); 399239268Sgonzo close(1); 400239268Sgonzo peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0); 401239268Sgonzo if (peer < 0) { 402239268Sgonzo tftp_log(LOG_ERR, "socket: %s", strerror(errno)); 403239268Sgonzo exit(1); 404239268Sgonzo } 405239268Sgonzo if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) { 406239268Sgonzo tftp_log(LOG_ERR, "bind: %s", strerror(errno)); 407239268Sgonzo exit(1); 408239268Sgonzo } 409239268Sgonzo 410239268Sgonzo tp = (struct tftphdr *)recvbuffer; 411239268Sgonzo tp->th_opcode = ntohs(tp->th_opcode); 412239268Sgonzo if (tp->th_opcode == RRQ) { 413239268Sgonzo if (allow_ro) 414239268Sgonzo tftp_rrq(peer, tp->th_stuff, n - 1); 415239268Sgonzo else { 416239268Sgonzo tftp_log(LOG_WARNING, 417239268Sgonzo "%s read access denied", peername); 418239268Sgonzo exit(1); 419239268Sgonzo } 420239268Sgonzo } 421239268Sgonzo if (tp->th_opcode == WRQ) { 422239268Sgonzo if (allow_wo) 423239268Sgonzo tftp_wrq(peer, tp->th_stuff, n - 1); 424239268Sgonzo else { 425239268Sgonzo tftp_log(LOG_WARNING, 426239268Sgonzo "%s write access denied", peername); 427239268Sgonzo exit(1); 428239268Sgonzo } 429239268Sgonzo } 430239268Sgonzo exit(1); 431239268Sgonzo} 432239268Sgonzo 433239268Sgonzostatic void 434239268Sgonzoreduce_path(char *fn) 435239268Sgonzo{ 436239268Sgonzo char *slash, *ptr; 437239268Sgonzo 438239268Sgonzo /* Reduce all "/+./" to "/" (just in case we've got "/./../" later */ 439239268Sgonzo while ((slash = strstr(fn, "/./")) != NULL) { 440239268Sgonzo for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 441239268Sgonzo ; 442239268Sgonzo slash += 2; 443239268Sgonzo while (*slash) 444239268Sgonzo *++ptr = *++slash; 445239268Sgonzo } 446239268Sgonzo 447239268Sgonzo /* Now reduce all "/something/+../" to "/" */ 448239268Sgonzo while ((slash = strstr(fn, "/../")) != NULL) { 449239268Sgonzo if (slash == fn) 450239268Sgonzo break; 451239268Sgonzo for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 452239268Sgonzo ; 453239268Sgonzo for (ptr--; ptr >= fn; ptr--) 454239268Sgonzo if (*ptr == '/') 455239268Sgonzo break; 456239268Sgonzo if (ptr < fn) 457239268Sgonzo break; 458239268Sgonzo slash += 3; 459239268Sgonzo while (*slash) 460239268Sgonzo *++ptr = *++slash; 461239268Sgonzo } 462239268Sgonzo} 463239268Sgonzo 464239268Sgonzostatic char * 465239268Sgonzoparse_header(int peer, char *recvbuffer, ssize_t size, 466239268Sgonzo char **filename, char **mode) 467239268Sgonzo{ 468239268Sgonzo char *cp; 469239268Sgonzo int i; 470244469Scognet struct formats *pf; 471244469Scognet 472239268Sgonzo *mode = NULL; 473239268Sgonzo cp = recvbuffer; 474239268Sgonzo 475239268Sgonzo i = get_field(peer, recvbuffer, size); 476239268Sgonzo if (i >= PATH_MAX) { 477239268Sgonzo tftp_log(LOG_ERR, "Bad option - filename too long"); 478239268Sgonzo send_error(peer, EBADOP); 479239268Sgonzo exit(1); 480239268Sgonzo } 481239268Sgonzo *filename = recvbuffer; 482239268Sgonzo tftp_log(LOG_INFO, "Filename: '%s'", *filename); 483239268Sgonzo cp += i; 484239268Sgonzo 485239268Sgonzo i = get_field(peer, cp, size); 486239268Sgonzo *mode = cp; 487239268Sgonzo cp += i; 488239268Sgonzo 489239268Sgonzo /* Find the file transfer mode */ 490239268Sgonzo for (cp = *mode; *cp; cp++) 491239268Sgonzo if (isupper(*cp)) 492239268Sgonzo *cp = tolower(*cp); 493239268Sgonzo for (pf = formats; pf->f_mode; pf++) 494239268Sgonzo if (strcmp(pf->f_mode, *mode) == 0) 495239268Sgonzo break; 496239268Sgonzo if (pf->f_mode == NULL) { 497239268Sgonzo tftp_log(LOG_ERR, 498239268Sgonzo "Bad option - Unknown transfer mode (%s)", *mode); 499239268Sgonzo send_error(peer, EBADOP); 500239268Sgonzo exit(1); 501239268Sgonzo } 502239268Sgonzo tftp_log(LOG_INFO, "Mode: '%s'", *mode); 503239268Sgonzo 504239268Sgonzo return (cp + 1); 505239268Sgonzo} 506239268Sgonzo 507239268Sgonzo/* 508239268Sgonzo * WRQ - receive a file from the client 509239268Sgonzo */ 510239268Sgonzovoid 511239268Sgonzotftp_wrq(int peer, char *recvbuffer, ssize_t size) 512239268Sgonzo{ 513239268Sgonzo char *cp; 514239268Sgonzo int has_options = 0, ecode; 515239268Sgonzo char *filename, *mode; 516239268Sgonzo char fnbuf[PATH_MAX]; 517239268Sgonzo 518239268Sgonzo cp = parse_header(peer, recvbuffer, size, &filename, &mode); 519239268Sgonzo size -= (cp - recvbuffer) + 1; 520239268Sgonzo 521239268Sgonzo strcpy(fnbuf, filename); 522239268Sgonzo reduce_path(fnbuf); 523239268Sgonzo filename = fnbuf; 524239268Sgonzo 525239268Sgonzo if (size > 0) { 526239268Sgonzo if (options_rfc_enabled) 527239268Sgonzo has_options = !parse_options(peer, cp, size); 528239268Sgonzo else 529239268Sgonzo tftp_log(LOG_INFO, "Options found but not enabled"); 530239268Sgonzo } 531239268Sgonzo 532239268Sgonzo ecode = validate_access(peer, &filename, WRQ); 533239268Sgonzo if (ecode == 0) { 534239268Sgonzo if (has_options) 535239268Sgonzo send_oack(peer); 536239268Sgonzo else 537239268Sgonzo send_ack(peer, 0); 538239268Sgonzo } 539239268Sgonzo if (logging) { 540239268Sgonzo tftp_log(LOG_INFO, "%s: write request for %s: %s", peername, 541239268Sgonzo filename, errtomsg(ecode)); 542239268Sgonzo } 543239268Sgonzo 544239268Sgonzo tftp_recvfile(peer, mode); 545239268Sgonzo exit(0); 546239268Sgonzo} 547239268Sgonzo 548239268Sgonzo/* 549239268Sgonzo * RRQ - send a file to the client 550239268Sgonzo */ 551239268Sgonzovoid 552239268Sgonzotftp_rrq(int peer, char *recvbuffer, ssize_t size) 553239268Sgonzo{ 554239268Sgonzo char *cp; 555239268Sgonzo int has_options = 0, ecode; 556239268Sgonzo char *filename, *mode; 557239268Sgonzo char fnbuf[PATH_MAX]; 558239268Sgonzo 559239268Sgonzo cp = parse_header(peer, recvbuffer, size, &filename, &mode); 560239268Sgonzo size -= (cp - recvbuffer) + 1; 561239268Sgonzo 562239268Sgonzo strcpy(fnbuf, filename); 563239268Sgonzo reduce_path(fnbuf); 564239268Sgonzo filename = fnbuf; 565239268Sgonzo 566239268Sgonzo if (size > 0) { 567239268Sgonzo if (options_rfc_enabled) 568239268Sgonzo has_options = !parse_options(peer, cp, size); 569239268Sgonzo else 570239268Sgonzo tftp_log(LOG_INFO, "Options found but not enabled"); 571239268Sgonzo } 572239268Sgonzo 573239268Sgonzo ecode = validate_access(peer, &filename, RRQ); 574239268Sgonzo if (ecode == 0) { 575239268Sgonzo if (has_options) { 576239268Sgonzo int n; 577239268Sgonzo char lrecvbuffer[MAXPKTSIZE]; 578239268Sgonzo struct tftphdr *rp = (struct tftphdr *)lrecvbuffer; 579239268Sgonzo 580239268Sgonzo send_oack(peer); 581239268Sgonzo n = receive_packet(peer, lrecvbuffer, MAXPKTSIZE, 582239268Sgonzo NULL, timeoutpacket); 583239268Sgonzo if (n < 0) { 584239268Sgonzo if (debug&DEBUG_SIMPLE) 585239268Sgonzo tftp_log(LOG_DEBUG, "Aborting: %s", 586239268Sgonzo rp_strerror(n)); 587239268Sgonzo return; 588239268Sgonzo } 589239268Sgonzo if (rp->th_opcode != ACK) { 590239268Sgonzo if (debug&DEBUG_SIMPLE) 591239268Sgonzo tftp_log(LOG_DEBUG, 592239268Sgonzo "Expected ACK, got %s on OACK", 593239268Sgonzo packettype(rp->th_opcode)); 594239268Sgonzo return; 595239268Sgonzo } 596239268Sgonzo } 597239268Sgonzo } 598239268Sgonzo 599239268Sgonzo if (logging) 600239268Sgonzo tftp_log(LOG_INFO, "%s: read request for %s: %s", peername, 601239268Sgonzo filename, errtomsg(ecode)); 602239268Sgonzo 603239268Sgonzo if (ecode) { 604239268Sgonzo /* 605244469Scognet * Avoid storms of naks to a RRQ broadcast for a relative 606244469Scognet * bootfile pathname from a diskless Sun. 607244469Scognet */ 608244469Scognet if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 609239268Sgonzo exit(0); 610239268Sgonzo send_error(peer, ecode); 611239268Sgonzo exit(1); 612239268Sgonzo } 613239268Sgonzo tftp_xmitfile(peer, mode); 614239268Sgonzo} 615239268Sgonzo 616239268Sgonzo/* 617239268Sgonzo * Find the next value for YYYYMMDD.nn when the file to be written should 618239268Sgonzo * be unique. Due to the limitations of nn, we will fail if nn reaches 100. 619239268Sgonzo * Besides, that is four updates per hour on a file, which is kind of 620239268Sgonzo * execessive anyway. 621239268Sgonzo */ 622239268Sgonzostatic int 623239268Sgonzofind_next_name(char *filename, int *fd) 624239268Sgonzo{ 625239268Sgonzo int i; 626239268Sgonzo time_t tval; 627239268Sgonzo size_t len; 628239268Sgonzo struct tm lt; 629239268Sgonzo char yyyymmdd[MAXPATHLEN]; 630239268Sgonzo char newname[MAXPATHLEN]; 631239268Sgonzo 632239268Sgonzo /* Create the YYYYMMDD part of the filename */ 633239268Sgonzo time(&tval); 634239268Sgonzo lt = *localtime(&tval); 635239268Sgonzo len = strftime(yyyymmdd, sizeof(yyyymmdd), newfile_format, <); 636239268Sgonzo if (len == 0) { 637239268Sgonzo syslog(LOG_WARNING, 638239268Sgonzo "Filename suffix too long (%d characters maximum)", 639239268Sgonzo MAXPATHLEN); 640239268Sgonzo return (EACCESS); 641239268Sgonzo } 642244469Scognet 643244469Scognet /* Make sure the new filename is not too long */ 644244469Scognet if (strlen(filename) > MAXPATHLEN - len - 5) { 645244469Scognet syslog(LOG_WARNING, 646244469Scognet "Filename too long (%zd characters, %zd maximum)", 647244469Scognet strlen(filename), MAXPATHLEN - len - 5); 648244469Scognet return (EACCESS); 649244469Scognet } 650244469Scognet 651244469Scognet /* Find the first file which doesn't exist */ 652244469Scognet for (i = 0; i < 100; i++) { 653244469Scognet sprintf(newname, "%s.%s.%02d", filename, yyyymmdd, i); 654244469Scognet *fd = open(newname, 655239268Sgonzo O_WRONLY | O_CREAT | O_EXCL, 656244469Scognet S_IRUSR | S_IWUSR | S_IRGRP | 657244469Scognet S_IWGRP | S_IROTH | S_IWOTH); 658244469Scognet if (*fd > 0) 659244469Scognet return 0; 660239268Sgonzo } 661244469Scognet 662244469Scognet return (EEXIST); 663244469Scognet} 664244469Scognet 665244469Scognet/* 666244469Scognet * Validate file access. Since we 667244469Scognet * have no uid or gid, for now require 668244469Scognet * file to exist and be publicly 669244469Scognet * readable/writable. 670244469Scognet * If we were invoked with arguments 671244469Scognet * from inetd then the file must also be 672244469Scognet * in one of the given directory prefixes. 673244469Scognet * Note also, full path name must be 674244469Scognet * given as we have no login directory. 675244469Scognet */ 676244469Scognetint 677244469Scognetvalidate_access(int peer, char **filep, int mode) 678244469Scognet{ 679244469Scognet struct stat stbuf; 680244469Scognet int fd; 681244469Scognet int error; 682244469Scognet struct dirlist *dirp; 683239268Sgonzo static char pathname[MAXPATHLEN]; 684244469Scognet char *filename = *filep; 685244469Scognet 686244469Scognet /* 687239268Sgonzo * Prevent tricksters from getting around the directory restrictions 688244469Scognet */ 689244469Scognet if (strstr(filename, "/../")) 690239268Sgonzo return (EACCESS); 691239268Sgonzo 692239268Sgonzo if (*filename == '/') { 693239268Sgonzo /* 694239268Sgonzo * Allow the request if it's in one of the approved locations. 695239268Sgonzo * Special case: check the null prefix ("/") by looking 696239268Sgonzo * for length = 1 and relying on the arg. processing that 697239268Sgonzo * it's a /. 698239268Sgonzo */ 699239268Sgonzo for (dirp = dirs; dirp->name != NULL; dirp++) { 700239268Sgonzo if (dirp->len == 1 || 701239268Sgonzo (!strncmp(filename, dirp->name, dirp->len) && 702239268Sgonzo filename[dirp->len] == '/')) 703239268Sgonzo break; 704239268Sgonzo } 705239268Sgonzo /* If directory list is empty, allow access to any file */ 706239268Sgonzo if (dirp->name == NULL && dirp != dirs) 707239268Sgonzo return (EACCESS); 708239268Sgonzo if (stat(filename, &stbuf) < 0) 709239268Sgonzo return (errno == ENOENT ? ENOTFOUND : EACCESS); 710239268Sgonzo if ((stbuf.st_mode & S_IFMT) != S_IFREG) 711239268Sgonzo return (ENOTFOUND); 712239268Sgonzo if (mode == RRQ) { 713244469Scognet if ((stbuf.st_mode & S_IROTH) == 0) 714244469Scognet return (EACCESS); 715239268Sgonzo } else { 716244469Scognet if ((stbuf.st_mode & S_IWOTH) == 0) 717244469Scognet return (EACCESS); 718244469Scognet } 719244469Scognet } else { 720244469Scognet int err; 721244469Scognet 722244469Scognet /* 723244469Scognet * Relative file name: search the approved locations for it. 724244469Scognet * Don't allow write requests that avoid directory 725244469Scognet * restrictions. 726244469Scognet */ 727244469Scognet 728244469Scognet if (!strncmp(filename, "../", 3)) 729244469Scognet return (EACCESS); 730244469Scognet 731239268Sgonzo /* 732239268Sgonzo * If the file exists in one of the directories and isn't 733239268Sgonzo * readable, continue looking. However, change the error code 734239268Sgonzo * to give an indication that the file exists. 735239268Sgonzo */ 736239268Sgonzo err = ENOTFOUND; 737239268Sgonzo for (dirp = dirs; dirp->name != NULL; dirp++) { 738239268Sgonzo snprintf(pathname, sizeof(pathname), "%s/%s", 739239268Sgonzo dirp->name, filename); 740239268Sgonzo if (stat(pathname, &stbuf) == 0 && 741239268Sgonzo (stbuf.st_mode & S_IFMT) == S_IFREG) { 742239268Sgonzo if ((stbuf.st_mode & S_IROTH) != 0) { 743239268Sgonzo break; 744239268Sgonzo } 745239268Sgonzo err = EACCESS; 746239268Sgonzo } 747239268Sgonzo } 748239268Sgonzo if (dirp->name != NULL) 749239268Sgonzo *filep = filename = pathname; 750239268Sgonzo else if (mode == RRQ) 751239268Sgonzo return (err); 752239268Sgonzo } 753239268Sgonzo 754239268Sgonzo /* 755239268Sgonzo * This option is handled here because it (might) require(s) the 756239268Sgonzo * size of the file. 757239268Sgonzo */ 758239268Sgonzo option_tsize(peer, NULL, mode, &stbuf); 759239268Sgonzo 760239268Sgonzo if (mode == RRQ) 761239268Sgonzo fd = open(filename, O_RDONLY); 762239268Sgonzo else { 763239268Sgonzo if (create_new) { 764239268Sgonzo if (increase_name) { 765239268Sgonzo error = find_next_name(filename, &fd); 766239268Sgonzo if (error > 0) 767239268Sgonzo return (error + 100); 768239268Sgonzo } else 769239268Sgonzo fd = open(filename, 770239268Sgonzo O_WRONLY | O_TRUNC | O_CREAT, 771239268Sgonzo S_IRUSR | S_IWUSR | S_IRGRP | 772239268Sgonzo S_IWGRP | S_IROTH | S_IWOTH ); 773239268Sgonzo } else 774239268Sgonzo fd = open(filename, O_WRONLY | O_TRUNC); 775239268Sgonzo } 776239268Sgonzo if (fd < 0) 777239268Sgonzo return (errno + 100); 778239268Sgonzo file = fdopen(fd, (mode == RRQ)? "r":"w"); 779239268Sgonzo if (file == NULL) { 780239268Sgonzo close(fd); 781239268Sgonzo return (errno + 100); 782239268Sgonzo } 783239268Sgonzo return (0); 784239268Sgonzo} 785239268Sgonzo 786239268Sgonzostatic void 787239268Sgonzotftp_xmitfile(int peer, const char *mode) 788239268Sgonzo{ 789239268Sgonzo uint16_t block; 790239268Sgonzo time_t now; 791239268Sgonzo struct tftp_stats ts; 792239268Sgonzo 793239268Sgonzo now = time(NULL); 794239268Sgonzo if (debug&DEBUG_SIMPLE) 795239268Sgonzo tftp_log(LOG_DEBUG, "Transmitting file"); 796239268Sgonzo 797239268Sgonzo read_init(0, file, mode); 798239268Sgonzo block = 1; 799239268Sgonzo tftp_send(peer, &block, &ts); 800239268Sgonzo read_close(); 801239268Sgonzo if (debug&DEBUG_SIMPLE) 802239268Sgonzo tftp_log(LOG_INFO, "Sent %d bytes in %d seconds", 803239268Sgonzo ts.amount, time(NULL) - now); 804239268Sgonzo} 805239268Sgonzo 806239268Sgonzostatic void 807239268Sgonzotftp_recvfile(int peer, const char *mode) 808239268Sgonzo{ 809239268Sgonzo uint16_t block; 810239268Sgonzo struct timeval now1, now2; 811239268Sgonzo struct tftp_stats ts; 812239268Sgonzo 813239268Sgonzo gettimeofday(&now1, NULL); 814239268Sgonzo if (debug&DEBUG_SIMPLE) 815239268Sgonzo tftp_log(LOG_DEBUG, "Receiving file"); 816239268Sgonzo 817239268Sgonzo write_init(0, file, mode); 818239268Sgonzo 819239268Sgonzo block = 0; 820239268Sgonzo tftp_receive(peer, &block, &ts, NULL, 0); 821239268Sgonzo 822239268Sgonzo write_close(); 823239268Sgonzo gettimeofday(&now2, NULL); 824239268Sgonzo 825239268Sgonzo if (debug&DEBUG_SIMPLE) { 826239268Sgonzo double f; 827239268Sgonzo if (now1.tv_usec > now2.tv_usec) { 828239268Sgonzo now2.tv_usec += 1000000; 829239268Sgonzo now2.tv_sec--; 830239268Sgonzo } 831239268Sgonzo 832239268Sgonzo f = now2.tv_sec - now1.tv_sec + 833239268Sgonzo (now2.tv_usec - now1.tv_usec) / 100000.0; 834239268Sgonzo tftp_log(LOG_INFO, 835239268Sgonzo "Download of %d bytes in %d blocks completed after %0.1f seconds\n", 836239268Sgonzo ts.amount, block, f); 837239268Sgonzo } 838239268Sgonzo 839239268Sgonzo return; 840239268Sgonzo} 841239268Sgonzo