tftpd.c revision 131358
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 4431512Scharnierstatic const char rcsid[] = 4550476Speter "$FreeBSD: head/libexec/tftpd/tftpd.c 131358 2004-06-30 18:58:19Z csjp $"; 461592Srgrimes#endif /* not lint */ 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> 5918458Simp#include <sys/types.h> 60130839Sbrian#include <sys/time.h> 611592Srgrimes 621592Srgrimes#include <netinet/in.h> 631592Srgrimes#include <arpa/tftp.h> 641592Srgrimes#include <arpa/inet.h> 651592Srgrimes 661592Srgrimes#include <ctype.h> 671592Srgrimes#include <errno.h> 681592Srgrimes#include <fcntl.h> 6945393Sbrian#include <libutil.h> 701592Srgrimes#include <netdb.h> 7131512Scharnier#include <pwd.h> 721592Srgrimes#include <setjmp.h> 731592Srgrimes#include <signal.h> 741592Srgrimes#include <stdio.h> 751592Srgrimes#include <stdlib.h> 761592Srgrimes#include <string.h> 771592Srgrimes#include <syslog.h> 781592Srgrimes#include <unistd.h> 791592Srgrimes 801592Srgrimes#include "tftpsubs.h" 811592Srgrimes 821592Srgrimes#define TIMEOUT 5 8384047Sobrien#define MAX_TIMEOUTS 5 841592Srgrimes 851592Srgrimesint peer; 861592Srgrimesint rexmtval = TIMEOUT; 8784047Sobrienint max_rexmtval = 2*TIMEOUT; 881592Srgrimes 891592Srgrimes#define PKTSIZE SEGSIZE+4 901592Srgrimeschar buf[PKTSIZE]; 911592Srgrimeschar ackbuf[PKTSIZE]; 9294443Sumestruct sockaddr_storage from; 931592Srgrimesint fromlen; 941592Srgrimes 9590333Simpvoid tftp(struct tftphdr *, int); 9694443Sumestatic void unmappedaddr(struct sockaddr_in6 *); 971592Srgrimes 981592Srgrimes/* 991592Srgrimes * Null-terminated directory prefix list for absolute pathname requests and 1001592Srgrimes * search list for relative pathname requests. 1011592Srgrimes * 1021592Srgrimes * MAXDIRS should be at least as large as the number of arguments that 1031592Srgrimes * inetd allows (currently 20). 1041592Srgrimes */ 1051592Srgrimes#define MAXDIRS 20 1061592Srgrimesstatic struct dirlist { 107112452Sdwmalone const char *name; 1081592Srgrimes int len; 1091592Srgrimes} dirs[MAXDIRS+1]; 1101592Srgrimesstatic int suppress_naks; 1111592Srgrimesstatic int logging; 11271616Sbillfstatic int ipchroot; 113129680Smdoddstatic int create_new = 0; 114129680Smdoddstatic mode_t mask = S_IWGRP|S_IWOTH; 1151592Srgrimes 116112452Sdwmalonestatic const char *errtomsg(int); 11790333Simpstatic void nak(int); 118112452Sdwmalonestatic void oack(void); 1191592Srgrimes 120112452Sdwmalonestatic void timer(int); 121112452Sdwmalonestatic void justquit(int); 122112452Sdwmalone 1231592Srgrimesint 12490333Simpmain(int argc, char *argv[]) 1251592Srgrimes{ 12690333Simp struct tftphdr *tp; 12790333Simp int n; 1281592Srgrimes int ch, on; 12994443Sume struct sockaddr_storage me; 13094443Sume int len; 13118458Simp char *chroot_dir = NULL; 13218458Simp struct passwd *nobody; 133112452Sdwmalone const char *chuser = "nobody"; 1341592Srgrimes 135130839Sbrian tzset(); /* syslog in localtime */ 136130839Sbrian 13735152Sphk openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 138129680Smdodd while ((ch = getopt(argc, argv, "cClns:u:Uw")) != -1) { 1391592Srgrimes switch (ch) { 14071616Sbillf case 'c': 14171616Sbillf ipchroot = 1; 14271616Sbillf break; 14371616Sbillf case 'C': 14471616Sbillf ipchroot = 2; 14571616Sbillf break; 1461592Srgrimes case 'l': 1471592Srgrimes logging = 1; 1481592Srgrimes break; 1491592Srgrimes case 'n': 1501592Srgrimes suppress_naks = 1; 1511592Srgrimes break; 15218458Simp case 's': 15318458Simp chroot_dir = optarg; 15418458Simp break; 15565850Swollman case 'u': 15665850Swollman chuser = optarg; 15765850Swollman break; 158129680Smdodd case 'U': 159129680Smdodd mask = strtol(optarg, NULL, 0); 160129680Smdodd break; 161129680Smdodd case 'w': 162129680Smdodd create_new = 1; 163129680Smdodd break; 1641592Srgrimes default: 1651592Srgrimes syslog(LOG_WARNING, "ignoring unknown option -%c", ch); 1661592Srgrimes } 1671592Srgrimes } 1681592Srgrimes if (optind < argc) { 1691592Srgrimes struct dirlist *dirp; 1701592Srgrimes 1711592Srgrimes /* Get list of directory prefixes. Skip relative pathnames. */ 1721592Srgrimes for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; 1731592Srgrimes optind++) { 1741592Srgrimes if (argv[optind][0] == '/') { 1751592Srgrimes dirp->name = argv[optind]; 1761592Srgrimes dirp->len = strlen(dirp->name); 1771592Srgrimes dirp++; 1781592Srgrimes } 1791592Srgrimes } 1801592Srgrimes } 18118458Simp else if (chroot_dir) { 18218458Simp dirs->name = "/"; 18318458Simp dirs->len = 1; 18418458Simp } 185113714Sbillf if (ipchroot > 0 && chroot_dir == NULL) { 18671616Sbillf syslog(LOG_ERR, "-c requires -s"); 18771616Sbillf exit(1); 18871616Sbillf } 1891592Srgrimes 190129680Smdodd umask(mask); 191129680Smdodd 1921592Srgrimes on = 1; 1931592Srgrimes if (ioctl(0, FIONBIO, &on) < 0) { 19431512Scharnier syslog(LOG_ERR, "ioctl(FIONBIO): %m"); 1951592Srgrimes exit(1); 1961592Srgrimes } 1971592Srgrimes fromlen = sizeof (from); 1981592Srgrimes n = recvfrom(0, buf, sizeof (buf), 0, 1991592Srgrimes (struct sockaddr *)&from, &fromlen); 2001592Srgrimes if (n < 0) { 20131512Scharnier syslog(LOG_ERR, "recvfrom: %m"); 2021592Srgrimes exit(1); 2031592Srgrimes } 2041592Srgrimes /* 2051592Srgrimes * Now that we have read the message out of the UDP 2061592Srgrimes * socket, we fork and exit. Thus, inetd will go back 2071592Srgrimes * to listening to the tftp port, and the next request 2081592Srgrimes * to come in will start up a new instance of tftpd. 2091592Srgrimes * 2101592Srgrimes * We do this so that inetd can run tftpd in "wait" mode. 2111592Srgrimes * The problem with tftpd running in "nowait" mode is that 2121592Srgrimes * inetd may get one or more successful "selects" on the 2131592Srgrimes * tftp port before we do our receive, so more than one 2141592Srgrimes * instance of tftpd may be started up. Worse, if tftpd 2151592Srgrimes * break before doing the above "recvfrom", inetd would 2161592Srgrimes * spawn endless instances, clogging the system. 2171592Srgrimes */ 2181592Srgrimes { 2191592Srgrimes int pid; 2201592Srgrimes int i, j; 2211592Srgrimes 2221592Srgrimes for (i = 1; i < 20; i++) { 2231592Srgrimes pid = fork(); 2241592Srgrimes if (pid < 0) { 2251592Srgrimes sleep(i); 2261592Srgrimes /* 2271592Srgrimes * flush out to most recently sent request. 2281592Srgrimes * 2291592Srgrimes * This may drop some request, but those 2301592Srgrimes * will be resent by the clients when 2311592Srgrimes * they timeout. The positive effect of 2321592Srgrimes * this flush is to (try to) prevent more 2331592Srgrimes * than one tftpd being started up to service 2341592Srgrimes * a single request from a single client. 2351592Srgrimes */ 2361592Srgrimes j = sizeof from; 2371592Srgrimes i = recvfrom(0, buf, sizeof (buf), 0, 2381592Srgrimes (struct sockaddr *)&from, &j); 2391592Srgrimes if (i > 0) { 2401592Srgrimes n = i; 2411592Srgrimes fromlen = j; 2421592Srgrimes } 2431592Srgrimes } else { 2441592Srgrimes break; 2451592Srgrimes } 2461592Srgrimes } 2471592Srgrimes if (pid < 0) { 24831512Scharnier syslog(LOG_ERR, "fork: %m"); 2491592Srgrimes exit(1); 2501592Srgrimes } else if (pid != 0) { 2511592Srgrimes exit(0); 2521592Srgrimes } 2531592Srgrimes } 25418458Simp 25518458Simp /* 25618458Simp * Since we exit here, we should do that only after the above 25718458Simp * recvfrom to keep inetd from constantly forking should there 25818458Simp * be a problem. See the above comment about system clogging. 25918458Simp */ 26018458Simp if (chroot_dir) { 261113714Sbillf if (ipchroot > 0) { 26271616Sbillf char *tempchroot; 26371616Sbillf struct stat sb; 26471616Sbillf int statret; 26594443Sume struct sockaddr_storage ss; 26694443Sume char hbuf[NI_MAXHOST]; 26771616Sbillf 26894443Sume memcpy(&ss, &from, from.ss_len); 26994443Sume unmappedaddr((struct sockaddr_in6 *)&ss); 27094443Sume getnameinfo((struct sockaddr *)&ss, ss.ss_len, 27194443Sume hbuf, sizeof(hbuf), NULL, 0, 27294443Sume NI_NUMERICHOST | NI_WITHSCOPEID); 27394443Sume asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); 274113714Sbillf if (ipchroot == 2) 275113714Sbillf statret = stat(tempchroot, &sb); 276113714Sbillf if (ipchroot == 1 || 277113714Sbillf (statret == 0 && (sb.st_mode & S_IFDIR))) 27871616Sbillf chroot_dir = tempchroot; 27971616Sbillf } 28018458Simp /* Must get this before chroot because /etc might go away */ 28165850Swollman if ((nobody = getpwnam(chuser)) == NULL) { 28265850Swollman syslog(LOG_ERR, "%s: no such user", chuser); 28318458Simp exit(1); 28418458Simp } 28518458Simp if (chroot(chroot_dir)) { 28618458Simp syslog(LOG_ERR, "chroot: %s: %m", chroot_dir); 28718458Simp exit(1); 28818458Simp } 289131358Scsjp chdir("/"); 290131358Scsjp setgroups(1, &nobody->pw_gid); 29118458Simp setuid(nobody->pw_uid); 29218458Simp } 29318458Simp 29494443Sume len = sizeof(me); 29594443Sume if (getsockname(0, (struct sockaddr *)&me, &len) == 0) { 29694443Sume switch (me.ss_family) { 29794443Sume case AF_INET: 29894443Sume ((struct sockaddr_in *)&me)->sin_port = 0; 29994443Sume break; 30094443Sume case AF_INET6: 30194443Sume ((struct sockaddr_in6 *)&me)->sin6_port = 0; 30294443Sume break; 30394443Sume default: 30494443Sume /* unsupported */ 30594443Sume break; 30694443Sume } 30794443Sume } else { 30894443Sume memset(&me, 0, sizeof(me)); 30994443Sume me.ss_family = from.ss_family; 31094443Sume me.ss_len = from.ss_len; 31194443Sume } 3121592Srgrimes alarm(0); 3131592Srgrimes close(0); 3141592Srgrimes close(1); 31594443Sume peer = socket(from.ss_family, SOCK_DGRAM, 0); 3161592Srgrimes if (peer < 0) { 31731512Scharnier syslog(LOG_ERR, "socket: %m"); 3181592Srgrimes exit(1); 3191592Srgrimes } 32094443Sume if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) { 32131512Scharnier syslog(LOG_ERR, "bind: %m"); 3221592Srgrimes exit(1); 3231592Srgrimes } 32494443Sume if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) { 32531512Scharnier syslog(LOG_ERR, "connect: %m"); 3261592Srgrimes exit(1); 3271592Srgrimes } 3281592Srgrimes tp = (struct tftphdr *)buf; 3291592Srgrimes tp->th_opcode = ntohs(tp->th_opcode); 3301592Srgrimes if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 3311592Srgrimes tftp(tp, n); 3321592Srgrimes exit(1); 3331592Srgrimes} 3341592Srgrimes 335130834Sbrianstatic void 336130834Sbrianreduce_path(char *fn) 337130834Sbrian{ 338130834Sbrian char *slash, *ptr; 339130834Sbrian 340130834Sbrian /* Reduce all "/+./" to "/" (just in case we've got "/./../" later */ 341130834Sbrian while ((slash = strstr(fn, "/./")) != NULL) { 342130834Sbrian for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 343130834Sbrian ; 344130834Sbrian slash += 2; 345130834Sbrian while (*slash) 346130834Sbrian *++ptr = *++slash; 347130834Sbrian } 348130834Sbrian 349130834Sbrian /* Now reduce all "/something/+../" to "/" */ 350130834Sbrian while ((slash = strstr(fn, "/../")) != NULL) { 351130834Sbrian if (slash == fn) 352130834Sbrian break; 353130834Sbrian for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 354130834Sbrian ; 355130834Sbrian for (ptr--; ptr >= fn; ptr--) 356130834Sbrian if (*ptr == '/') 357130834Sbrian break; 358130834Sbrian if (ptr < fn) 359130834Sbrian break; 360130834Sbrian slash += 3; 361130834Sbrian while (*slash) 362130834Sbrian *++ptr = *++slash; 363130834Sbrian } 364130834Sbrian} 365130834Sbrian 3661592Srgrimesstruct formats; 36790333Simpint validate_access(char **, int); 36890333Simpvoid xmitfile(struct formats *); 36990333Simpvoid recvfile(struct formats *); 3701592Srgrimes 3711592Srgrimesstruct formats { 372112452Sdwmalone const char *f_mode; 37390333Simp int (*f_validate)(char **, int); 37490333Simp void (*f_send)(struct formats *); 37590333Simp void (*f_recv)(struct formats *); 3761592Srgrimes int f_convert; 3771592Srgrimes} formats[] = { 37840765Sdg { "netascii", validate_access, xmitfile, recvfile, 1 }, 37940765Sdg { "octet", validate_access, xmitfile, recvfile, 0 }, 3801592Srgrimes#ifdef notdef 3811592Srgrimes { "mail", validate_user, sendmail, recvmail, 1 }, 3821592Srgrimes#endif 383112452Sdwmalone { 0, NULL, NULL, NULL, 0 } 3841592Srgrimes}; 3851592Srgrimes 38684047Sobrienstruct options { 387112452Sdwmalone const char *o_type; 38884047Sobrien char *o_request; 38984047Sobrien int o_reply; /* turn into union if need be */ 39084047Sobrien} options[] = { 391112452Sdwmalone { "tsize", NULL, 0 }, /* OPT_TSIZE */ 392112452Sdwmalone { "timeout", NULL, 0 }, /* OPT_TIMEOUT */ 393112452Sdwmalone { NULL, NULL, 0 } 39484047Sobrien}; 39584047Sobrien 39684047Sobrienenum opt_enum { 39784047Sobrien OPT_TSIZE = 0, 39884047Sobrien OPT_TIMEOUT, 39984047Sobrien}; 40084047Sobrien 4011592Srgrimes/* 4021592Srgrimes * Handle initial connection protocol. 4031592Srgrimes */ 4041592Srgrimesvoid 40590333Simptftp(struct tftphdr *tp, int size) 4061592Srgrimes{ 40790333Simp char *cp; 40884047Sobrien int i, first = 1, has_options = 0, ecode; 40990333Simp struct formats *pf; 41084047Sobrien char *filename, *mode, *option, *ccp; 411130834Sbrian char fnbuf[PATH_MAX], resolved_fnbuf[PATH_MAX]; 4121592Srgrimes 413122916Ssobomax cp = tp->th_stuff; 4141592Srgrimesagain: 4151592Srgrimes while (cp < buf + size) { 4161592Srgrimes if (*cp == '\0') 4171592Srgrimes break; 4181592Srgrimes cp++; 4191592Srgrimes } 4201592Srgrimes if (*cp != '\0') { 4211592Srgrimes nak(EBADOP); 4221592Srgrimes exit(1); 4231592Srgrimes } 424122916Ssobomax i = cp - tp->th_stuff; 425122916Ssobomax if (i >= sizeof(fnbuf)) { 426122916Ssobomax nak(EBADOP); 427122916Ssobomax exit(1); 428122916Ssobomax } 429122916Ssobomax memcpy(fnbuf, tp->th_stuff, i); 430122916Ssobomax fnbuf[i] = '\0'; 431130834Sbrian reduce_path(fnbuf); 432122916Ssobomax filename = fnbuf; 4331592Srgrimes if (first) { 4341592Srgrimes mode = ++cp; 4351592Srgrimes first = 0; 4361592Srgrimes goto again; 4371592Srgrimes } 4381592Srgrimes for (cp = mode; *cp; cp++) 4391592Srgrimes if (isupper(*cp)) 4401592Srgrimes *cp = tolower(*cp); 4411592Srgrimes for (pf = formats; pf->f_mode; pf++) 4421592Srgrimes if (strcmp(pf->f_mode, mode) == 0) 4431592Srgrimes break; 4441592Srgrimes if (pf->f_mode == 0) { 4451592Srgrimes nak(EBADOP); 4461592Srgrimes exit(1); 4471592Srgrimes } 44884047Sobrien while (++cp < buf + size) { 44984047Sobrien for (i = 2, ccp = cp; i > 0; ccp++) { 45084047Sobrien if (ccp >= buf + size) { 45186765Sbenno /* 45286765Sbenno * Don't reject the request, just stop trying 45386765Sbenno * to parse the option and get on with it. 45486765Sbenno * Some Apple OpenFirmware versions have 45586765Sbenno * trailing garbage on the end of otherwise 45686765Sbenno * valid requests. 45786765Sbenno */ 45886765Sbenno goto option_fail; 45984047Sobrien } else if (*ccp == '\0') 46084047Sobrien i--; 46184047Sobrien } 46284047Sobrien for (option = cp; *cp; cp++) 46384047Sobrien if (isupper(*cp)) 46484047Sobrien *cp = tolower(*cp); 46584047Sobrien for (i = 0; options[i].o_type != NULL; i++) 46684047Sobrien if (strcmp(option, options[i].o_type) == 0) { 46784047Sobrien options[i].o_request = ++cp; 46884047Sobrien has_options = 1; 46984047Sobrien } 47084047Sobrien cp = ccp-1; 47184047Sobrien } 47284047Sobrien 47386765Sbennooption_fail: 47484047Sobrien if (options[OPT_TIMEOUT].o_request) { 47584047Sobrien int to = atoi(options[OPT_TIMEOUT].o_request); 47684047Sobrien if (to < 1 || to > 255) { 47784047Sobrien nak(EBADOP); 47884047Sobrien exit(1); 47984047Sobrien } 48084047Sobrien else if (to <= max_rexmtval) 48184047Sobrien options[OPT_TIMEOUT].o_reply = rexmtval = to; 48284047Sobrien else 48384047Sobrien options[OPT_TIMEOUT].o_request = NULL; 48484047Sobrien } 48584047Sobrien 4861592Srgrimes ecode = (*pf->f_validate)(&filename, tp->th_opcode); 487130834Sbrian if (has_options && ecode == 0) 48884047Sobrien oack(); 4891592Srgrimes if (logging) { 49094443Sume char hbuf[NI_MAXHOST]; 49145393Sbrian 49294443Sume getnameinfo((struct sockaddr *)&from, from.ss_len, 49394443Sume hbuf, sizeof(hbuf), NULL, 0, 49494443Sume NI_WITHSCOPEID); 49594443Sume syslog(LOG_INFO, "%s: %s request for %s: %s", hbuf, 4961592Srgrimes tp->th_opcode == WRQ ? "write" : "read", 4971592Srgrimes filename, errtomsg(ecode)); 4981592Srgrimes } 4991592Srgrimes if (ecode) { 5001592Srgrimes /* 5011592Srgrimes * Avoid storms of naks to a RRQ broadcast for a relative 5021592Srgrimes * bootfile pathname from a diskless Sun. 5031592Srgrimes */ 5041592Srgrimes if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 5051592Srgrimes exit(0); 5061592Srgrimes nak(ecode); 5071592Srgrimes exit(1); 5081592Srgrimes } 5091592Srgrimes if (tp->th_opcode == WRQ) 5101592Srgrimes (*pf->f_recv)(pf); 5111592Srgrimes else 5121592Srgrimes (*pf->f_send)(pf); 5131592Srgrimes exit(0); 5141592Srgrimes} 5151592Srgrimes 5161592Srgrimes 5171592SrgrimesFILE *file; 5181592Srgrimes 5191592Srgrimes/* 5201592Srgrimes * Validate file access. Since we 5211592Srgrimes * have no uid or gid, for now require 5221592Srgrimes * file to exist and be publicly 5231592Srgrimes * readable/writable. 5241592Srgrimes * If we were invoked with arguments 5251592Srgrimes * from inetd then the file must also be 5261592Srgrimes * in one of the given directory prefixes. 5271592Srgrimes * Note also, full path name must be 5281592Srgrimes * given as we have no login directory. 5291592Srgrimes */ 5301592Srgrimesint 53190333Simpvalidate_access(char **filep, int mode) 5321592Srgrimes{ 5331592Srgrimes struct stat stbuf; 5341592Srgrimes int fd; 5351592Srgrimes struct dirlist *dirp; 5361592Srgrimes static char pathname[MAXPATHLEN]; 5371592Srgrimes char *filename = *filep; 5381592Srgrimes 5391592Srgrimes /* 5401592Srgrimes * Prevent tricksters from getting around the directory restrictions 5411592Srgrimes */ 5421592Srgrimes if (strstr(filename, "/../")) 5431592Srgrimes return (EACCESS); 5441592Srgrimes 5451592Srgrimes if (*filename == '/') { 5461592Srgrimes /* 5471592Srgrimes * Allow the request if it's in one of the approved locations. 5481592Srgrimes * Special case: check the null prefix ("/") by looking 5491592Srgrimes * for length = 1 and relying on the arg. processing that 5501592Srgrimes * it's a /. 5511592Srgrimes */ 5521592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 5531592Srgrimes if (dirp->len == 1 || 5541592Srgrimes (!strncmp(filename, dirp->name, dirp->len) && 5551592Srgrimes filename[dirp->len] == '/')) 5561592Srgrimes break; 5571592Srgrimes } 5581592Srgrimes /* If directory list is empty, allow access to any file */ 5591592Srgrimes if (dirp->name == NULL && dirp != dirs) 5601592Srgrimes return (EACCESS); 5611592Srgrimes if (stat(filename, &stbuf) < 0) 5621592Srgrimes return (errno == ENOENT ? ENOTFOUND : EACCESS); 5631592Srgrimes if ((stbuf.st_mode & S_IFMT) != S_IFREG) 5641592Srgrimes return (ENOTFOUND); 5651592Srgrimes if (mode == RRQ) { 5661592Srgrimes if ((stbuf.st_mode & S_IROTH) == 0) 5671592Srgrimes return (EACCESS); 5681592Srgrimes } else { 5691592Srgrimes if ((stbuf.st_mode & S_IWOTH) == 0) 5701592Srgrimes return (EACCESS); 5711592Srgrimes } 5721592Srgrimes } else { 5731592Srgrimes int err; 5741592Srgrimes 5751592Srgrimes /* 5761592Srgrimes * Relative file name: search the approved locations for it. 5776750Sjkh * Don't allow write requests that avoid directory 5781592Srgrimes * restrictions. 5791592Srgrimes */ 5801592Srgrimes 5816750Sjkh if (!strncmp(filename, "../", 3)) 5821592Srgrimes return (EACCESS); 5831592Srgrimes 5841592Srgrimes /* 5851592Srgrimes * If the file exists in one of the directories and isn't 5861592Srgrimes * readable, continue looking. However, change the error code 5871592Srgrimes * to give an indication that the file exists. 5881592Srgrimes */ 5891592Srgrimes err = ENOTFOUND; 5901592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 59124193Simp snprintf(pathname, sizeof(pathname), "%s/%s", 59224193Simp dirp->name, filename); 5931592Srgrimes if (stat(pathname, &stbuf) == 0 && 5941592Srgrimes (stbuf.st_mode & S_IFMT) == S_IFREG) { 5951592Srgrimes if ((stbuf.st_mode & S_IROTH) != 0) { 5961592Srgrimes break; 5971592Srgrimes } 5981592Srgrimes err = EACCESS; 5991592Srgrimes } 6001592Srgrimes } 601129680Smdodd if (dirp->name != NULL) 602129680Smdodd *filep = filename = pathname; 603129680Smdodd else if (mode == RRQ) 6041592Srgrimes return (err); 6051592Srgrimes } 60684047Sobrien if (options[OPT_TSIZE].o_request) { 60784047Sobrien if (mode == RRQ) 60884047Sobrien options[OPT_TSIZE].o_reply = stbuf.st_size; 60984047Sobrien else 61084047Sobrien /* XXX Allows writes of all sizes. */ 61184047Sobrien options[OPT_TSIZE].o_reply = 61284047Sobrien atoi(options[OPT_TSIZE].o_request); 61384047Sobrien } 614129680Smdodd if (mode == RRQ) 615129680Smdodd fd = open(filename, O_RDONLY); 616129680Smdodd else { 617129680Smdodd if (create_new) 618129680Smdodd fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0666); 619129680Smdodd else 620129680Smdodd fd = open(filename, O_WRONLY|O_TRUNC); 621129680Smdodd } 6221592Srgrimes if (fd < 0) 6231592Srgrimes return (errno + 100); 6241592Srgrimes file = fdopen(fd, (mode == RRQ)? "r":"w"); 6251592Srgrimes if (file == NULL) { 626129683Smdodd close(fd); 627129683Smdodd return (errno + 100); 6281592Srgrimes } 6291592Srgrimes return (0); 6301592Srgrimes} 6311592Srgrimes 63284047Sobrienint timeouts; 6331592Srgrimesjmp_buf timeoutbuf; 6341592Srgrimes 6351592Srgrimesvoid 63690333Simptimer(int sig __unused) 6371592Srgrimes{ 63884047Sobrien if (++timeouts > MAX_TIMEOUTS) 6391592Srgrimes exit(1); 6401592Srgrimes longjmp(timeoutbuf, 1); 6411592Srgrimes} 6421592Srgrimes 6431592Srgrimes/* 6441592Srgrimes * Send the requested file. 6451592Srgrimes */ 6461592Srgrimesvoid 64790333Simpxmitfile(struct formats *pf) 6481592Srgrimes{ 649112452Sdwmalone struct tftphdr *dp; 65090333Simp struct tftphdr *ap; /* ack packet */ 65190333Simp int size, n; 65271926Sasmodai volatile unsigned short block; 6531592Srgrimes 6541592Srgrimes signal(SIGALRM, timer); 6551592Srgrimes dp = r_init(); 6561592Srgrimes ap = (struct tftphdr *)ackbuf; 6571592Srgrimes block = 1; 6581592Srgrimes do { 6591592Srgrimes size = readit(file, &dp, pf->f_convert); 6601592Srgrimes if (size < 0) { 6611592Srgrimes nak(errno + 100); 6621592Srgrimes goto abort; 6631592Srgrimes } 6641592Srgrimes dp->th_opcode = htons((u_short)DATA); 6651592Srgrimes dp->th_block = htons((u_short)block); 66684047Sobrien timeouts = 0; 6671592Srgrimes (void)setjmp(timeoutbuf); 6681592Srgrimes 6691592Srgrimessend_data: 67094299Sambrisko { 67194299Sambrisko int i, t = 1; 67294299Sambrisko for (i = 0; ; i++){ 67394299Sambrisko if (send(peer, dp, size + 4, 0) != size + 4) { 67494299Sambrisko sleep(t); 67594299Sambrisko t = (t < 32) ? t<< 1 : t; 67694299Sambrisko if (i >= 12) { 67794299Sambrisko syslog(LOG_ERR, "write: %m"); 67894299Sambrisko goto abort; 67994299Sambrisko } 68094299Sambrisko } 68194299Sambrisko break; 68294299Sambrisko } 6831592Srgrimes } 6841592Srgrimes read_ahead(file, pf->f_convert); 6851592Srgrimes for ( ; ; ) { 6861592Srgrimes alarm(rexmtval); /* read the ack */ 6871592Srgrimes n = recv(peer, ackbuf, sizeof (ackbuf), 0); 6881592Srgrimes alarm(0); 6891592Srgrimes if (n < 0) { 69031512Scharnier syslog(LOG_ERR, "read: %m"); 6911592Srgrimes goto abort; 6921592Srgrimes } 6931592Srgrimes ap->th_opcode = ntohs((u_short)ap->th_opcode); 6941592Srgrimes ap->th_block = ntohs((u_short)ap->th_block); 6951592Srgrimes 6961592Srgrimes if (ap->th_opcode == ERROR) 6971592Srgrimes goto abort; 6981592Srgrimes 6991592Srgrimes if (ap->th_opcode == ACK) { 7001592Srgrimes if (ap->th_block == block) 7011592Srgrimes break; 7021592Srgrimes /* Re-synchronize with the other side */ 7031592Srgrimes (void) synchnet(peer); 7041592Srgrimes if (ap->th_block == (block -1)) 7051592Srgrimes goto send_data; 7061592Srgrimes } 7071592Srgrimes 7081592Srgrimes } 7091592Srgrimes block++; 7101592Srgrimes } while (size == SEGSIZE); 7111592Srgrimesabort: 7121592Srgrimes (void) fclose(file); 7131592Srgrimes} 7141592Srgrimes 7151592Srgrimesvoid 71690333Simpjustquit(int sig __unused) 7171592Srgrimes{ 7181592Srgrimes exit(0); 7191592Srgrimes} 7201592Srgrimes 7211592Srgrimes 7221592Srgrimes/* 7231592Srgrimes * Receive a file. 7241592Srgrimes */ 7251592Srgrimesvoid 72690333Simprecvfile(struct formats *pf) 7271592Srgrimes{ 728112452Sdwmalone struct tftphdr *dp; 72990333Simp struct tftphdr *ap; /* ack buffer */ 73090333Simp int n, size; 73171926Sasmodai volatile unsigned short block; 7321592Srgrimes 7331592Srgrimes signal(SIGALRM, timer); 7341592Srgrimes dp = w_init(); 7351592Srgrimes ap = (struct tftphdr *)ackbuf; 7361592Srgrimes block = 0; 7371592Srgrimes do { 73884047Sobrien timeouts = 0; 7391592Srgrimes ap->th_opcode = htons((u_short)ACK); 7401592Srgrimes ap->th_block = htons((u_short)block); 7411592Srgrimes block++; 7421592Srgrimes (void) setjmp(timeoutbuf); 7431592Srgrimessend_ack: 7441592Srgrimes if (send(peer, ackbuf, 4, 0) != 4) { 74531512Scharnier syslog(LOG_ERR, "write: %m"); 7461592Srgrimes goto abort; 7471592Srgrimes } 7481592Srgrimes write_behind(file, pf->f_convert); 7491592Srgrimes for ( ; ; ) { 7501592Srgrimes alarm(rexmtval); 7511592Srgrimes n = recv(peer, dp, PKTSIZE, 0); 7521592Srgrimes alarm(0); 7531592Srgrimes if (n < 0) { /* really? */ 75431512Scharnier syslog(LOG_ERR, "read: %m"); 7551592Srgrimes goto abort; 7561592Srgrimes } 7571592Srgrimes dp->th_opcode = ntohs((u_short)dp->th_opcode); 7581592Srgrimes dp->th_block = ntohs((u_short)dp->th_block); 7591592Srgrimes if (dp->th_opcode == ERROR) 7601592Srgrimes goto abort; 7611592Srgrimes if (dp->th_opcode == DATA) { 7621592Srgrimes if (dp->th_block == block) { 7631592Srgrimes break; /* normal */ 7641592Srgrimes } 7651592Srgrimes /* Re-synchronize with the other side */ 7661592Srgrimes (void) synchnet(peer); 7671592Srgrimes if (dp->th_block == (block-1)) 7681592Srgrimes goto send_ack; /* rexmit */ 7691592Srgrimes } 7701592Srgrimes } 7711592Srgrimes /* size = write(file, dp->th_data, n - 4); */ 7721592Srgrimes size = writeit(file, &dp, n - 4, pf->f_convert); 7731592Srgrimes if (size != (n-4)) { /* ahem */ 7741592Srgrimes if (size < 0) nak(errno + 100); 7751592Srgrimes else nak(ENOSPACE); 7761592Srgrimes goto abort; 7771592Srgrimes } 7781592Srgrimes } while (size == SEGSIZE); 7791592Srgrimes write_behind(file, pf->f_convert); 7801592Srgrimes (void) fclose(file); /* close data file */ 7811592Srgrimes 7821592Srgrimes ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 7831592Srgrimes ap->th_block = htons((u_short)(block)); 7841592Srgrimes (void) send(peer, ackbuf, 4, 0); 7851592Srgrimes 7861592Srgrimes signal(SIGALRM, justquit); /* just quit on timeout */ 7871592Srgrimes alarm(rexmtval); 7881592Srgrimes n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 7891592Srgrimes alarm(0); 7901592Srgrimes if (n >= 4 && /* if read some data */ 7911592Srgrimes dp->th_opcode == DATA && /* and got a data block */ 7921592Srgrimes block == dp->th_block) { /* then my last ack was lost */ 7931592Srgrimes (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 7941592Srgrimes } 7951592Srgrimesabort: 7961592Srgrimes return; 7971592Srgrimes} 7981592Srgrimes 7991592Srgrimesstruct errmsg { 8001592Srgrimes int e_code; 801112452Sdwmalone const char *e_msg; 8021592Srgrimes} errmsgs[] = { 8031592Srgrimes { EUNDEF, "Undefined error code" }, 8041592Srgrimes { ENOTFOUND, "File not found" }, 8051592Srgrimes { EACCESS, "Access violation" }, 8061592Srgrimes { ENOSPACE, "Disk full or allocation exceeded" }, 8071592Srgrimes { EBADOP, "Illegal TFTP operation" }, 8081592Srgrimes { EBADID, "Unknown transfer ID" }, 8091592Srgrimes { EEXISTS, "File already exists" }, 8101592Srgrimes { ENOUSER, "No such user" }, 81184047Sobrien { EOPTNEG, "Option negotiation" }, 8121592Srgrimes { -1, 0 } 8131592Srgrimes}; 8141592Srgrimes 815112452Sdwmalonestatic const char * 81690333Simperrtomsg(int error) 8171592Srgrimes{ 818112452Sdwmalone static char ebuf[20]; 81990333Simp struct errmsg *pe; 8201592Srgrimes if (error == 0) 8211592Srgrimes return "success"; 8221592Srgrimes for (pe = errmsgs; pe->e_code >= 0; pe++) 8231592Srgrimes if (pe->e_code == error) 8241592Srgrimes return pe->e_msg; 825112452Sdwmalone snprintf(ebuf, sizeof(buf), "error %d", error); 826112452Sdwmalone return ebuf; 8271592Srgrimes} 8281592Srgrimes 8291592Srgrimes/* 8301592Srgrimes * Send a nak packet (error message). 8311592Srgrimes * Error code passed in is one of the 8321592Srgrimes * standard TFTP codes, or a UNIX errno 8331592Srgrimes * offset by 100. 8341592Srgrimes */ 8351592Srgrimesstatic void 83690333Simpnak(int error) 8371592Srgrimes{ 83890333Simp struct tftphdr *tp; 8391592Srgrimes int length; 84090333Simp struct errmsg *pe; 8411592Srgrimes 8421592Srgrimes tp = (struct tftphdr *)buf; 8431592Srgrimes tp->th_opcode = htons((u_short)ERROR); 8441592Srgrimes tp->th_code = htons((u_short)error); 8451592Srgrimes for (pe = errmsgs; pe->e_code >= 0; pe++) 8461592Srgrimes if (pe->e_code == error) 8471592Srgrimes break; 8481592Srgrimes if (pe->e_code < 0) { 8491592Srgrimes pe->e_msg = strerror(error - 100); 8501592Srgrimes tp->th_code = EUNDEF; /* set 'undef' errorcode */ 8511592Srgrimes } 8521592Srgrimes strcpy(tp->th_msg, pe->e_msg); 8531592Srgrimes length = strlen(pe->e_msg); 8541592Srgrimes tp->th_msg[length] = '\0'; 8551592Srgrimes length += 5; 8561592Srgrimes if (send(peer, buf, length, 0) != length) 85731512Scharnier syslog(LOG_ERR, "nak: %m"); 8581592Srgrimes} 85984047Sobrien 86094443Sume/* translate IPv4 mapped IPv6 address to IPv4 address */ 86194443Sumestatic void 86294443Sumeunmappedaddr(struct sockaddr_in6 *sin6) 86394443Sume{ 86495496Sume struct sockaddr_in *sin4; 86595496Sume u_int32_t addr; 86695496Sume int port; 86794443Sume 86895496Sume if (sin6->sin6_family != AF_INET6 || 86995496Sume !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 87095496Sume return; 87195496Sume sin4 = (struct sockaddr_in *)sin6; 87295496Sume addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 87395496Sume port = sin6->sin6_port; 87495496Sume memset(sin4, 0, sizeof(struct sockaddr_in)); 87595496Sume sin4->sin_addr.s_addr = addr; 87695496Sume sin4->sin_port = port; 87795496Sume sin4->sin_family = AF_INET; 87895496Sume sin4->sin_len = sizeof(struct sockaddr_in); 87994443Sume} 88094443Sume 88184047Sobrien/* 88284047Sobrien * Send an oack packet (option acknowledgement). 88384047Sobrien */ 88484047Sobrienstatic void 88590333Simpoack(void) 88684047Sobrien{ 88784047Sobrien struct tftphdr *tp, *ap; 88884047Sobrien int size, i, n; 88984047Sobrien char *bp; 89084047Sobrien 89184047Sobrien tp = (struct tftphdr *)buf; 89284047Sobrien bp = buf + 2; 89384047Sobrien size = sizeof(buf) - 2; 89484047Sobrien tp->th_opcode = htons((u_short)OACK); 89584047Sobrien for (i = 0; options[i].o_type != NULL; i++) { 89684047Sobrien if (options[i].o_request) { 89784047Sobrien n = snprintf(bp, size, "%s%c%d", options[i].o_type, 89884047Sobrien 0, options[i].o_reply); 89984047Sobrien bp += n+1; 90084047Sobrien size -= n+1; 90184047Sobrien if (size < 0) { 90284047Sobrien syslog(LOG_ERR, "oack: buffer overflow"); 90384047Sobrien exit(1); 90484047Sobrien } 90584047Sobrien } 90684047Sobrien } 90784047Sobrien size = bp - buf; 90884047Sobrien ap = (struct tftphdr *)ackbuf; 90984047Sobrien signal(SIGALRM, timer); 91084047Sobrien timeouts = 0; 91184047Sobrien 91284047Sobrien (void)setjmp(timeoutbuf); 91384047Sobrien if (send(peer, buf, size, 0) != size) { 91484047Sobrien syslog(LOG_INFO, "oack: %m"); 91584047Sobrien exit(1); 91684047Sobrien } 91784047Sobrien 91884047Sobrien for (;;) { 91984047Sobrien alarm(rexmtval); 92084047Sobrien n = recv(peer, ackbuf, sizeof (ackbuf), 0); 92184047Sobrien alarm(0); 92284047Sobrien if (n < 0) { 92384047Sobrien syslog(LOG_ERR, "recv: %m"); 92484047Sobrien exit(1); 92584047Sobrien } 92684047Sobrien ap->th_opcode = ntohs((u_short)ap->th_opcode); 92784047Sobrien ap->th_block = ntohs((u_short)ap->th_block); 92884047Sobrien if (ap->th_opcode == ERROR) 92984047Sobrien exit(1); 93084047Sobrien if (ap->th_opcode == ACK && ap->th_block == 0) 93184047Sobrien break; 93284047Sobrien } 93384047Sobrien} 934