tftpd.c revision 122916
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 122916 2003-11-20 13:36:31Z sobomax $"; 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> 601592Srgrimes 611592Srgrimes#include <netinet/in.h> 621592Srgrimes#include <arpa/tftp.h> 631592Srgrimes#include <arpa/inet.h> 641592Srgrimes 651592Srgrimes#include <ctype.h> 661592Srgrimes#include <errno.h> 671592Srgrimes#include <fcntl.h> 6845393Sbrian#include <libutil.h> 691592Srgrimes#include <netdb.h> 7031512Scharnier#include <pwd.h> 711592Srgrimes#include <setjmp.h> 721592Srgrimes#include <signal.h> 731592Srgrimes#include <stdio.h> 741592Srgrimes#include <stdlib.h> 751592Srgrimes#include <string.h> 761592Srgrimes#include <syslog.h> 771592Srgrimes#include <unistd.h> 781592Srgrimes 791592Srgrimes#include "tftpsubs.h" 801592Srgrimes 811592Srgrimes#define TIMEOUT 5 8284047Sobrien#define MAX_TIMEOUTS 5 831592Srgrimes 841592Srgrimesint peer; 851592Srgrimesint rexmtval = TIMEOUT; 8684047Sobrienint max_rexmtval = 2*TIMEOUT; 871592Srgrimes 881592Srgrimes#define PKTSIZE SEGSIZE+4 891592Srgrimeschar buf[PKTSIZE]; 901592Srgrimeschar ackbuf[PKTSIZE]; 9194443Sumestruct sockaddr_storage from; 921592Srgrimesint fromlen; 931592Srgrimes 9490333Simpvoid tftp(struct tftphdr *, int); 9594443Sumestatic void unmappedaddr(struct sockaddr_in6 *); 961592Srgrimes 971592Srgrimes/* 981592Srgrimes * Null-terminated directory prefix list for absolute pathname requests and 991592Srgrimes * search list for relative pathname requests. 1001592Srgrimes * 1011592Srgrimes * MAXDIRS should be at least as large as the number of arguments that 1021592Srgrimes * inetd allows (currently 20). 1031592Srgrimes */ 1041592Srgrimes#define MAXDIRS 20 1051592Srgrimesstatic struct dirlist { 106112452Sdwmalone const char *name; 1071592Srgrimes int len; 1081592Srgrimes} dirs[MAXDIRS+1]; 1091592Srgrimesstatic int suppress_naks; 1101592Srgrimesstatic int logging; 11171616Sbillfstatic int ipchroot; 1121592Srgrimes 113112452Sdwmalonestatic const char *errtomsg(int); 11490333Simpstatic void nak(int); 115112452Sdwmalonestatic void oack(void); 1161592Srgrimes 117112452Sdwmalonestatic void timer(int); 118112452Sdwmalonestatic void justquit(int); 119112452Sdwmalone 1201592Srgrimesint 12190333Simpmain(int argc, char *argv[]) 1221592Srgrimes{ 12390333Simp struct tftphdr *tp; 12490333Simp int n; 1251592Srgrimes int ch, on; 12694443Sume struct sockaddr_storage me; 12794443Sume int len; 12818458Simp char *chroot_dir = NULL; 12918458Simp struct passwd *nobody; 130112452Sdwmalone const char *chuser = "nobody"; 1311592Srgrimes 13235152Sphk openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 13371616Sbillf while ((ch = getopt(argc, argv, "cClns:u:")) != -1) { 1341592Srgrimes switch (ch) { 13571616Sbillf case 'c': 13671616Sbillf ipchroot = 1; 13771616Sbillf break; 13871616Sbillf case 'C': 13971616Sbillf ipchroot = 2; 14071616Sbillf break; 1411592Srgrimes case 'l': 1421592Srgrimes logging = 1; 1431592Srgrimes break; 1441592Srgrimes case 'n': 1451592Srgrimes suppress_naks = 1; 1461592Srgrimes break; 14718458Simp case 's': 14818458Simp chroot_dir = optarg; 14918458Simp break; 15065850Swollman case 'u': 15165850Swollman chuser = optarg; 15265850Swollman break; 1531592Srgrimes default: 1541592Srgrimes syslog(LOG_WARNING, "ignoring unknown option -%c", ch); 1551592Srgrimes } 1561592Srgrimes } 1571592Srgrimes if (optind < argc) { 1581592Srgrimes struct dirlist *dirp; 1591592Srgrimes 1601592Srgrimes /* Get list of directory prefixes. Skip relative pathnames. */ 1611592Srgrimes for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; 1621592Srgrimes optind++) { 1631592Srgrimes if (argv[optind][0] == '/') { 1641592Srgrimes dirp->name = argv[optind]; 1651592Srgrimes dirp->len = strlen(dirp->name); 1661592Srgrimes dirp++; 1671592Srgrimes } 1681592Srgrimes } 1691592Srgrimes } 17018458Simp else if (chroot_dir) { 17118458Simp dirs->name = "/"; 17218458Simp dirs->len = 1; 17318458Simp } 174113714Sbillf if (ipchroot > 0 && chroot_dir == NULL) { 17571616Sbillf syslog(LOG_ERR, "-c requires -s"); 17671616Sbillf exit(1); 17771616Sbillf } 1781592Srgrimes 1791592Srgrimes on = 1; 1801592Srgrimes if (ioctl(0, FIONBIO, &on) < 0) { 18131512Scharnier syslog(LOG_ERR, "ioctl(FIONBIO): %m"); 1821592Srgrimes exit(1); 1831592Srgrimes } 1841592Srgrimes fromlen = sizeof (from); 1851592Srgrimes n = recvfrom(0, buf, sizeof (buf), 0, 1861592Srgrimes (struct sockaddr *)&from, &fromlen); 1871592Srgrimes if (n < 0) { 18831512Scharnier syslog(LOG_ERR, "recvfrom: %m"); 1891592Srgrimes exit(1); 1901592Srgrimes } 1911592Srgrimes /* 1921592Srgrimes * Now that we have read the message out of the UDP 1931592Srgrimes * socket, we fork and exit. Thus, inetd will go back 1941592Srgrimes * to listening to the tftp port, and the next request 1951592Srgrimes * to come in will start up a new instance of tftpd. 1961592Srgrimes * 1971592Srgrimes * We do this so that inetd can run tftpd in "wait" mode. 1981592Srgrimes * The problem with tftpd running in "nowait" mode is that 1991592Srgrimes * inetd may get one or more successful "selects" on the 2001592Srgrimes * tftp port before we do our receive, so more than one 2011592Srgrimes * instance of tftpd may be started up. Worse, if tftpd 2021592Srgrimes * break before doing the above "recvfrom", inetd would 2031592Srgrimes * spawn endless instances, clogging the system. 2041592Srgrimes */ 2051592Srgrimes { 2061592Srgrimes int pid; 2071592Srgrimes int i, j; 2081592Srgrimes 2091592Srgrimes for (i = 1; i < 20; i++) { 2101592Srgrimes pid = fork(); 2111592Srgrimes if (pid < 0) { 2121592Srgrimes sleep(i); 2131592Srgrimes /* 2141592Srgrimes * flush out to most recently sent request. 2151592Srgrimes * 2161592Srgrimes * This may drop some request, but those 2171592Srgrimes * will be resent by the clients when 2181592Srgrimes * they timeout. The positive effect of 2191592Srgrimes * this flush is to (try to) prevent more 2201592Srgrimes * than one tftpd being started up to service 2211592Srgrimes * a single request from a single client. 2221592Srgrimes */ 2231592Srgrimes j = sizeof from; 2241592Srgrimes i = recvfrom(0, buf, sizeof (buf), 0, 2251592Srgrimes (struct sockaddr *)&from, &j); 2261592Srgrimes if (i > 0) { 2271592Srgrimes n = i; 2281592Srgrimes fromlen = j; 2291592Srgrimes } 2301592Srgrimes } else { 2311592Srgrimes break; 2321592Srgrimes } 2331592Srgrimes } 2341592Srgrimes if (pid < 0) { 23531512Scharnier syslog(LOG_ERR, "fork: %m"); 2361592Srgrimes exit(1); 2371592Srgrimes } else if (pid != 0) { 2381592Srgrimes exit(0); 2391592Srgrimes } 2401592Srgrimes } 24118458Simp 24218458Simp /* 24318458Simp * Since we exit here, we should do that only after the above 24418458Simp * recvfrom to keep inetd from constantly forking should there 24518458Simp * be a problem. See the above comment about system clogging. 24618458Simp */ 24718458Simp if (chroot_dir) { 248113714Sbillf if (ipchroot > 0) { 24971616Sbillf char *tempchroot; 25071616Sbillf struct stat sb; 25171616Sbillf int statret; 25294443Sume struct sockaddr_storage ss; 25394443Sume char hbuf[NI_MAXHOST]; 25471616Sbillf 25594443Sume memcpy(&ss, &from, from.ss_len); 25694443Sume unmappedaddr((struct sockaddr_in6 *)&ss); 25794443Sume getnameinfo((struct sockaddr *)&ss, ss.ss_len, 25894443Sume hbuf, sizeof(hbuf), NULL, 0, 25994443Sume NI_NUMERICHOST | NI_WITHSCOPEID); 26094443Sume asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); 261113714Sbillf if (ipchroot == 2) 262113714Sbillf statret = stat(tempchroot, &sb); 263113714Sbillf if (ipchroot == 1 || 264113714Sbillf (statret == 0 && (sb.st_mode & S_IFDIR))) 26571616Sbillf chroot_dir = tempchroot; 26671616Sbillf } 26718458Simp /* Must get this before chroot because /etc might go away */ 26865850Swollman if ((nobody = getpwnam(chuser)) == NULL) { 26965850Swollman syslog(LOG_ERR, "%s: no such user", chuser); 27018458Simp exit(1); 27118458Simp } 27218458Simp if (chroot(chroot_dir)) { 27318458Simp syslog(LOG_ERR, "chroot: %s: %m", chroot_dir); 27418458Simp exit(1); 27518458Simp } 27618458Simp chdir( "/" ); 27718458Simp setuid(nobody->pw_uid); 27885299Sobrien setgroups(1, &nobody->pw_gid); 27918458Simp } 28018458Simp 28194443Sume len = sizeof(me); 28294443Sume if (getsockname(0, (struct sockaddr *)&me, &len) == 0) { 28394443Sume switch (me.ss_family) { 28494443Sume case AF_INET: 28594443Sume ((struct sockaddr_in *)&me)->sin_port = 0; 28694443Sume break; 28794443Sume case AF_INET6: 28894443Sume ((struct sockaddr_in6 *)&me)->sin6_port = 0; 28994443Sume break; 29094443Sume default: 29194443Sume /* unsupported */ 29294443Sume break; 29394443Sume } 29494443Sume } else { 29594443Sume memset(&me, 0, sizeof(me)); 29694443Sume me.ss_family = from.ss_family; 29794443Sume me.ss_len = from.ss_len; 29894443Sume } 2991592Srgrimes alarm(0); 3001592Srgrimes close(0); 3011592Srgrimes close(1); 30294443Sume peer = socket(from.ss_family, SOCK_DGRAM, 0); 3031592Srgrimes if (peer < 0) { 30431512Scharnier syslog(LOG_ERR, "socket: %m"); 3051592Srgrimes exit(1); 3061592Srgrimes } 30794443Sume if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) { 30831512Scharnier syslog(LOG_ERR, "bind: %m"); 3091592Srgrimes exit(1); 3101592Srgrimes } 31194443Sume if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) { 31231512Scharnier syslog(LOG_ERR, "connect: %m"); 3131592Srgrimes exit(1); 3141592Srgrimes } 3151592Srgrimes tp = (struct tftphdr *)buf; 3161592Srgrimes tp->th_opcode = ntohs(tp->th_opcode); 3171592Srgrimes if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 3181592Srgrimes tftp(tp, n); 3191592Srgrimes exit(1); 3201592Srgrimes} 3211592Srgrimes 3221592Srgrimesstruct formats; 32390333Simpint validate_access(char **, int); 32490333Simpvoid xmitfile(struct formats *); 32590333Simpvoid recvfile(struct formats *); 3261592Srgrimes 3271592Srgrimesstruct formats { 328112452Sdwmalone const char *f_mode; 32990333Simp int (*f_validate)(char **, int); 33090333Simp void (*f_send)(struct formats *); 33190333Simp void (*f_recv)(struct formats *); 3321592Srgrimes int f_convert; 3331592Srgrimes} formats[] = { 33440765Sdg { "netascii", validate_access, xmitfile, recvfile, 1 }, 33540765Sdg { "octet", validate_access, xmitfile, recvfile, 0 }, 3361592Srgrimes#ifdef notdef 3371592Srgrimes { "mail", validate_user, sendmail, recvmail, 1 }, 3381592Srgrimes#endif 339112452Sdwmalone { 0, NULL, NULL, NULL, 0 } 3401592Srgrimes}; 3411592Srgrimes 34284047Sobrienstruct options { 343112452Sdwmalone const char *o_type; 34484047Sobrien char *o_request; 34584047Sobrien int o_reply; /* turn into union if need be */ 34684047Sobrien} options[] = { 347112452Sdwmalone { "tsize", NULL, 0 }, /* OPT_TSIZE */ 348112452Sdwmalone { "timeout", NULL, 0 }, /* OPT_TIMEOUT */ 349112452Sdwmalone { NULL, NULL, 0 } 35084047Sobrien}; 35184047Sobrien 35284047Sobrienenum opt_enum { 35384047Sobrien OPT_TSIZE = 0, 35484047Sobrien OPT_TIMEOUT, 35584047Sobrien}; 35684047Sobrien 3571592Srgrimes/* 3581592Srgrimes * Handle initial connection protocol. 3591592Srgrimes */ 3601592Srgrimesvoid 36190333Simptftp(struct tftphdr *tp, int size) 3621592Srgrimes{ 36390333Simp char *cp; 36484047Sobrien int i, first = 1, has_options = 0, ecode; 36590333Simp struct formats *pf; 36684047Sobrien char *filename, *mode, *option, *ccp; 367122916Ssobomax char fnbuf[MAXPATHLEN]; 3681592Srgrimes 369122916Ssobomax cp = tp->th_stuff; 3701592Srgrimesagain: 3711592Srgrimes while (cp < buf + size) { 3721592Srgrimes if (*cp == '\0') 3731592Srgrimes break; 3741592Srgrimes cp++; 3751592Srgrimes } 3761592Srgrimes if (*cp != '\0') { 3771592Srgrimes nak(EBADOP); 3781592Srgrimes exit(1); 3791592Srgrimes } 380122916Ssobomax i = cp - tp->th_stuff; 381122916Ssobomax if (i >= sizeof(fnbuf)) { 382122916Ssobomax nak(EBADOP); 383122916Ssobomax exit(1); 384122916Ssobomax } 385122916Ssobomax memcpy(fnbuf, tp->th_stuff, i); 386122916Ssobomax fnbuf[i] = '\0'; 387122916Ssobomax filename = fnbuf; 3881592Srgrimes if (first) { 3891592Srgrimes mode = ++cp; 3901592Srgrimes first = 0; 3911592Srgrimes goto again; 3921592Srgrimes } 3931592Srgrimes for (cp = mode; *cp; cp++) 3941592Srgrimes if (isupper(*cp)) 3951592Srgrimes *cp = tolower(*cp); 3961592Srgrimes for (pf = formats; pf->f_mode; pf++) 3971592Srgrimes if (strcmp(pf->f_mode, mode) == 0) 3981592Srgrimes break; 3991592Srgrimes if (pf->f_mode == 0) { 4001592Srgrimes nak(EBADOP); 4011592Srgrimes exit(1); 4021592Srgrimes } 40384047Sobrien while (++cp < buf + size) { 40484047Sobrien for (i = 2, ccp = cp; i > 0; ccp++) { 40584047Sobrien if (ccp >= buf + size) { 40686765Sbenno /* 40786765Sbenno * Don't reject the request, just stop trying 40886765Sbenno * to parse the option and get on with it. 40986765Sbenno * Some Apple OpenFirmware versions have 41086765Sbenno * trailing garbage on the end of otherwise 41186765Sbenno * valid requests. 41286765Sbenno */ 41386765Sbenno goto option_fail; 41484047Sobrien } else if (*ccp == '\0') 41584047Sobrien i--; 41684047Sobrien } 41784047Sobrien for (option = cp; *cp; cp++) 41884047Sobrien if (isupper(*cp)) 41984047Sobrien *cp = tolower(*cp); 42084047Sobrien for (i = 0; options[i].o_type != NULL; i++) 42184047Sobrien if (strcmp(option, options[i].o_type) == 0) { 42284047Sobrien options[i].o_request = ++cp; 42384047Sobrien has_options = 1; 42484047Sobrien } 42584047Sobrien cp = ccp-1; 42684047Sobrien } 42784047Sobrien 42886765Sbennooption_fail: 42984047Sobrien if (options[OPT_TIMEOUT].o_request) { 43084047Sobrien int to = atoi(options[OPT_TIMEOUT].o_request); 43184047Sobrien if (to < 1 || to > 255) { 43284047Sobrien nak(EBADOP); 43384047Sobrien exit(1); 43484047Sobrien } 43584047Sobrien else if (to <= max_rexmtval) 43684047Sobrien options[OPT_TIMEOUT].o_reply = rexmtval = to; 43784047Sobrien else 43884047Sobrien options[OPT_TIMEOUT].o_request = NULL; 43984047Sobrien } 44084047Sobrien 4411592Srgrimes ecode = (*pf->f_validate)(&filename, tp->th_opcode); 44284047Sobrien if (has_options) 44384047Sobrien oack(); 4441592Srgrimes if (logging) { 44594443Sume char hbuf[NI_MAXHOST]; 44645393Sbrian 44794443Sume getnameinfo((struct sockaddr *)&from, from.ss_len, 44894443Sume hbuf, sizeof(hbuf), NULL, 0, 44994443Sume NI_WITHSCOPEID); 45094443Sume syslog(LOG_INFO, "%s: %s request for %s: %s", hbuf, 4511592Srgrimes tp->th_opcode == WRQ ? "write" : "read", 4521592Srgrimes filename, errtomsg(ecode)); 4531592Srgrimes } 4541592Srgrimes if (ecode) { 4551592Srgrimes /* 4561592Srgrimes * Avoid storms of naks to a RRQ broadcast for a relative 4571592Srgrimes * bootfile pathname from a diskless Sun. 4581592Srgrimes */ 4591592Srgrimes if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 4601592Srgrimes exit(0); 4611592Srgrimes nak(ecode); 4621592Srgrimes exit(1); 4631592Srgrimes } 4641592Srgrimes if (tp->th_opcode == WRQ) 4651592Srgrimes (*pf->f_recv)(pf); 4661592Srgrimes else 4671592Srgrimes (*pf->f_send)(pf); 4681592Srgrimes exit(0); 4691592Srgrimes} 4701592Srgrimes 4711592Srgrimes 4721592SrgrimesFILE *file; 4731592Srgrimes 4741592Srgrimes/* 4751592Srgrimes * Validate file access. Since we 4761592Srgrimes * have no uid or gid, for now require 4771592Srgrimes * file to exist and be publicly 4781592Srgrimes * readable/writable. 4791592Srgrimes * If we were invoked with arguments 4801592Srgrimes * from inetd then the file must also be 4811592Srgrimes * in one of the given directory prefixes. 4821592Srgrimes * Note also, full path name must be 4831592Srgrimes * given as we have no login directory. 4841592Srgrimes */ 4851592Srgrimesint 48690333Simpvalidate_access(char **filep, int mode) 4871592Srgrimes{ 4881592Srgrimes struct stat stbuf; 4891592Srgrimes int fd; 4901592Srgrimes struct dirlist *dirp; 4911592Srgrimes static char pathname[MAXPATHLEN]; 4921592Srgrimes char *filename = *filep; 4931592Srgrimes 4941592Srgrimes /* 4951592Srgrimes * Prevent tricksters from getting around the directory restrictions 4961592Srgrimes */ 4971592Srgrimes if (strstr(filename, "/../")) 4981592Srgrimes return (EACCESS); 4991592Srgrimes 5001592Srgrimes if (*filename == '/') { 5011592Srgrimes /* 5021592Srgrimes * Allow the request if it's in one of the approved locations. 5031592Srgrimes * Special case: check the null prefix ("/") by looking 5041592Srgrimes * for length = 1 and relying on the arg. processing that 5051592Srgrimes * it's a /. 5061592Srgrimes */ 5071592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 5081592Srgrimes if (dirp->len == 1 || 5091592Srgrimes (!strncmp(filename, dirp->name, dirp->len) && 5101592Srgrimes filename[dirp->len] == '/')) 5111592Srgrimes break; 5121592Srgrimes } 5131592Srgrimes /* If directory list is empty, allow access to any file */ 5141592Srgrimes if (dirp->name == NULL && dirp != dirs) 5151592Srgrimes return (EACCESS); 5161592Srgrimes if (stat(filename, &stbuf) < 0) 5171592Srgrimes return (errno == ENOENT ? ENOTFOUND : EACCESS); 5181592Srgrimes if ((stbuf.st_mode & S_IFMT) != S_IFREG) 5191592Srgrimes return (ENOTFOUND); 5201592Srgrimes if (mode == RRQ) { 5211592Srgrimes if ((stbuf.st_mode & S_IROTH) == 0) 5221592Srgrimes return (EACCESS); 5231592Srgrimes } else { 5241592Srgrimes if ((stbuf.st_mode & S_IWOTH) == 0) 5251592Srgrimes return (EACCESS); 5261592Srgrimes } 5271592Srgrimes } else { 5281592Srgrimes int err; 5291592Srgrimes 5301592Srgrimes /* 5311592Srgrimes * Relative file name: search the approved locations for it. 5326750Sjkh * Don't allow write requests that avoid directory 5331592Srgrimes * restrictions. 5341592Srgrimes */ 5351592Srgrimes 5366750Sjkh if (!strncmp(filename, "../", 3)) 5371592Srgrimes return (EACCESS); 5381592Srgrimes 5391592Srgrimes /* 5401592Srgrimes * If the file exists in one of the directories and isn't 5411592Srgrimes * readable, continue looking. However, change the error code 5421592Srgrimes * to give an indication that the file exists. 5431592Srgrimes */ 5441592Srgrimes err = ENOTFOUND; 5451592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 54624193Simp snprintf(pathname, sizeof(pathname), "%s/%s", 54724193Simp dirp->name, filename); 5481592Srgrimes if (stat(pathname, &stbuf) == 0 && 5491592Srgrimes (stbuf.st_mode & S_IFMT) == S_IFREG) { 5501592Srgrimes if ((stbuf.st_mode & S_IROTH) != 0) { 5511592Srgrimes break; 5521592Srgrimes } 5531592Srgrimes err = EACCESS; 5541592Srgrimes } 5551592Srgrimes } 5561592Srgrimes if (dirp->name == NULL) 5571592Srgrimes return (err); 5581592Srgrimes *filep = filename = pathname; 5591592Srgrimes } 56084047Sobrien if (options[OPT_TSIZE].o_request) { 56184047Sobrien if (mode == RRQ) 56284047Sobrien options[OPT_TSIZE].o_reply = stbuf.st_size; 56384047Sobrien else 56484047Sobrien /* XXX Allows writes of all sizes. */ 56584047Sobrien options[OPT_TSIZE].o_reply = 56684047Sobrien atoi(options[OPT_TSIZE].o_request); 56784047Sobrien } 56820052Sjoerg fd = open(filename, mode == RRQ ? O_RDONLY : O_WRONLY|O_TRUNC); 5691592Srgrimes if (fd < 0) 5701592Srgrimes return (errno + 100); 5711592Srgrimes file = fdopen(fd, (mode == RRQ)? "r":"w"); 5721592Srgrimes if (file == NULL) { 5731592Srgrimes return errno+100; 5741592Srgrimes } 5751592Srgrimes return (0); 5761592Srgrimes} 5771592Srgrimes 57884047Sobrienint timeouts; 5791592Srgrimesjmp_buf timeoutbuf; 5801592Srgrimes 5811592Srgrimesvoid 58290333Simptimer(int sig __unused) 5831592Srgrimes{ 58484047Sobrien if (++timeouts > MAX_TIMEOUTS) 5851592Srgrimes exit(1); 5861592Srgrimes longjmp(timeoutbuf, 1); 5871592Srgrimes} 5881592Srgrimes 5891592Srgrimes/* 5901592Srgrimes * Send the requested file. 5911592Srgrimes */ 5921592Srgrimesvoid 59390333Simpxmitfile(struct formats *pf) 5941592Srgrimes{ 595112452Sdwmalone struct tftphdr *dp; 59690333Simp struct tftphdr *ap; /* ack packet */ 59790333Simp int size, n; 59871926Sasmodai volatile unsigned short block; 5991592Srgrimes 6001592Srgrimes signal(SIGALRM, timer); 6011592Srgrimes dp = r_init(); 6021592Srgrimes ap = (struct tftphdr *)ackbuf; 6031592Srgrimes block = 1; 6041592Srgrimes do { 6051592Srgrimes size = readit(file, &dp, pf->f_convert); 6061592Srgrimes if (size < 0) { 6071592Srgrimes nak(errno + 100); 6081592Srgrimes goto abort; 6091592Srgrimes } 6101592Srgrimes dp->th_opcode = htons((u_short)DATA); 6111592Srgrimes dp->th_block = htons((u_short)block); 61284047Sobrien timeouts = 0; 6131592Srgrimes (void)setjmp(timeoutbuf); 6141592Srgrimes 6151592Srgrimessend_data: 61694299Sambrisko { 61794299Sambrisko int i, t = 1; 61894299Sambrisko for (i = 0; ; i++){ 61994299Sambrisko if (send(peer, dp, size + 4, 0) != size + 4) { 62094299Sambrisko sleep(t); 62194299Sambrisko t = (t < 32) ? t<< 1 : t; 62294299Sambrisko if (i >= 12) { 62394299Sambrisko syslog(LOG_ERR, "write: %m"); 62494299Sambrisko goto abort; 62594299Sambrisko } 62694299Sambrisko } 62794299Sambrisko break; 62894299Sambrisko } 6291592Srgrimes } 6301592Srgrimes read_ahead(file, pf->f_convert); 6311592Srgrimes for ( ; ; ) { 6321592Srgrimes alarm(rexmtval); /* read the ack */ 6331592Srgrimes n = recv(peer, ackbuf, sizeof (ackbuf), 0); 6341592Srgrimes alarm(0); 6351592Srgrimes if (n < 0) { 63631512Scharnier syslog(LOG_ERR, "read: %m"); 6371592Srgrimes goto abort; 6381592Srgrimes } 6391592Srgrimes ap->th_opcode = ntohs((u_short)ap->th_opcode); 6401592Srgrimes ap->th_block = ntohs((u_short)ap->th_block); 6411592Srgrimes 6421592Srgrimes if (ap->th_opcode == ERROR) 6431592Srgrimes goto abort; 6441592Srgrimes 6451592Srgrimes if (ap->th_opcode == ACK) { 6461592Srgrimes if (ap->th_block == block) 6471592Srgrimes break; 6481592Srgrimes /* Re-synchronize with the other side */ 6491592Srgrimes (void) synchnet(peer); 6501592Srgrimes if (ap->th_block == (block -1)) 6511592Srgrimes goto send_data; 6521592Srgrimes } 6531592Srgrimes 6541592Srgrimes } 6551592Srgrimes block++; 6561592Srgrimes } while (size == SEGSIZE); 6571592Srgrimesabort: 6581592Srgrimes (void) fclose(file); 6591592Srgrimes} 6601592Srgrimes 6611592Srgrimesvoid 66290333Simpjustquit(int sig __unused) 6631592Srgrimes{ 6641592Srgrimes exit(0); 6651592Srgrimes} 6661592Srgrimes 6671592Srgrimes 6681592Srgrimes/* 6691592Srgrimes * Receive a file. 6701592Srgrimes */ 6711592Srgrimesvoid 67290333Simprecvfile(struct formats *pf) 6731592Srgrimes{ 674112452Sdwmalone struct tftphdr *dp; 67590333Simp struct tftphdr *ap; /* ack buffer */ 67690333Simp int n, size; 67771926Sasmodai volatile unsigned short block; 6781592Srgrimes 6791592Srgrimes signal(SIGALRM, timer); 6801592Srgrimes dp = w_init(); 6811592Srgrimes ap = (struct tftphdr *)ackbuf; 6821592Srgrimes block = 0; 6831592Srgrimes do { 68484047Sobrien timeouts = 0; 6851592Srgrimes ap->th_opcode = htons((u_short)ACK); 6861592Srgrimes ap->th_block = htons((u_short)block); 6871592Srgrimes block++; 6881592Srgrimes (void) setjmp(timeoutbuf); 6891592Srgrimessend_ack: 6901592Srgrimes if (send(peer, ackbuf, 4, 0) != 4) { 69131512Scharnier syslog(LOG_ERR, "write: %m"); 6921592Srgrimes goto abort; 6931592Srgrimes } 6941592Srgrimes write_behind(file, pf->f_convert); 6951592Srgrimes for ( ; ; ) { 6961592Srgrimes alarm(rexmtval); 6971592Srgrimes n = recv(peer, dp, PKTSIZE, 0); 6981592Srgrimes alarm(0); 6991592Srgrimes if (n < 0) { /* really? */ 70031512Scharnier syslog(LOG_ERR, "read: %m"); 7011592Srgrimes goto abort; 7021592Srgrimes } 7031592Srgrimes dp->th_opcode = ntohs((u_short)dp->th_opcode); 7041592Srgrimes dp->th_block = ntohs((u_short)dp->th_block); 7051592Srgrimes if (dp->th_opcode == ERROR) 7061592Srgrimes goto abort; 7071592Srgrimes if (dp->th_opcode == DATA) { 7081592Srgrimes if (dp->th_block == block) { 7091592Srgrimes break; /* normal */ 7101592Srgrimes } 7111592Srgrimes /* Re-synchronize with the other side */ 7121592Srgrimes (void) synchnet(peer); 7131592Srgrimes if (dp->th_block == (block-1)) 7141592Srgrimes goto send_ack; /* rexmit */ 7151592Srgrimes } 7161592Srgrimes } 7171592Srgrimes /* size = write(file, dp->th_data, n - 4); */ 7181592Srgrimes size = writeit(file, &dp, n - 4, pf->f_convert); 7191592Srgrimes if (size != (n-4)) { /* ahem */ 7201592Srgrimes if (size < 0) nak(errno + 100); 7211592Srgrimes else nak(ENOSPACE); 7221592Srgrimes goto abort; 7231592Srgrimes } 7241592Srgrimes } while (size == SEGSIZE); 7251592Srgrimes write_behind(file, pf->f_convert); 7261592Srgrimes (void) fclose(file); /* close data file */ 7271592Srgrimes 7281592Srgrimes ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 7291592Srgrimes ap->th_block = htons((u_short)(block)); 7301592Srgrimes (void) send(peer, ackbuf, 4, 0); 7311592Srgrimes 7321592Srgrimes signal(SIGALRM, justquit); /* just quit on timeout */ 7331592Srgrimes alarm(rexmtval); 7341592Srgrimes n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 7351592Srgrimes alarm(0); 7361592Srgrimes if (n >= 4 && /* if read some data */ 7371592Srgrimes dp->th_opcode == DATA && /* and got a data block */ 7381592Srgrimes block == dp->th_block) { /* then my last ack was lost */ 7391592Srgrimes (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 7401592Srgrimes } 7411592Srgrimesabort: 7421592Srgrimes return; 7431592Srgrimes} 7441592Srgrimes 7451592Srgrimesstruct errmsg { 7461592Srgrimes int e_code; 747112452Sdwmalone const char *e_msg; 7481592Srgrimes} errmsgs[] = { 7491592Srgrimes { EUNDEF, "Undefined error code" }, 7501592Srgrimes { ENOTFOUND, "File not found" }, 7511592Srgrimes { EACCESS, "Access violation" }, 7521592Srgrimes { ENOSPACE, "Disk full or allocation exceeded" }, 7531592Srgrimes { EBADOP, "Illegal TFTP operation" }, 7541592Srgrimes { EBADID, "Unknown transfer ID" }, 7551592Srgrimes { EEXISTS, "File already exists" }, 7561592Srgrimes { ENOUSER, "No such user" }, 75784047Sobrien { EOPTNEG, "Option negotiation" }, 7581592Srgrimes { -1, 0 } 7591592Srgrimes}; 7601592Srgrimes 761112452Sdwmalonestatic const char * 76290333Simperrtomsg(int error) 7631592Srgrimes{ 764112452Sdwmalone static char ebuf[20]; 76590333Simp struct errmsg *pe; 7661592Srgrimes if (error == 0) 7671592Srgrimes return "success"; 7681592Srgrimes for (pe = errmsgs; pe->e_code >= 0; pe++) 7691592Srgrimes if (pe->e_code == error) 7701592Srgrimes return pe->e_msg; 771112452Sdwmalone snprintf(ebuf, sizeof(buf), "error %d", error); 772112452Sdwmalone return ebuf; 7731592Srgrimes} 7741592Srgrimes 7751592Srgrimes/* 7761592Srgrimes * Send a nak packet (error message). 7771592Srgrimes * Error code passed in is one of the 7781592Srgrimes * standard TFTP codes, or a UNIX errno 7791592Srgrimes * offset by 100. 7801592Srgrimes */ 7811592Srgrimesstatic void 78290333Simpnak(int error) 7831592Srgrimes{ 78490333Simp struct tftphdr *tp; 7851592Srgrimes int length; 78690333Simp struct errmsg *pe; 7871592Srgrimes 7881592Srgrimes tp = (struct tftphdr *)buf; 7891592Srgrimes tp->th_opcode = htons((u_short)ERROR); 7901592Srgrimes tp->th_code = htons((u_short)error); 7911592Srgrimes for (pe = errmsgs; pe->e_code >= 0; pe++) 7921592Srgrimes if (pe->e_code == error) 7931592Srgrimes break; 7941592Srgrimes if (pe->e_code < 0) { 7951592Srgrimes pe->e_msg = strerror(error - 100); 7961592Srgrimes tp->th_code = EUNDEF; /* set 'undef' errorcode */ 7971592Srgrimes } 7981592Srgrimes strcpy(tp->th_msg, pe->e_msg); 7991592Srgrimes length = strlen(pe->e_msg); 8001592Srgrimes tp->th_msg[length] = '\0'; 8011592Srgrimes length += 5; 8021592Srgrimes if (send(peer, buf, length, 0) != length) 80331512Scharnier syslog(LOG_ERR, "nak: %m"); 8041592Srgrimes} 80584047Sobrien 80694443Sume/* translate IPv4 mapped IPv6 address to IPv4 address */ 80794443Sumestatic void 80894443Sumeunmappedaddr(struct sockaddr_in6 *sin6) 80994443Sume{ 81095496Sume struct sockaddr_in *sin4; 81195496Sume u_int32_t addr; 81295496Sume int port; 81394443Sume 81495496Sume if (sin6->sin6_family != AF_INET6 || 81595496Sume !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 81695496Sume return; 81795496Sume sin4 = (struct sockaddr_in *)sin6; 81895496Sume addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 81995496Sume port = sin6->sin6_port; 82095496Sume memset(sin4, 0, sizeof(struct sockaddr_in)); 82195496Sume sin4->sin_addr.s_addr = addr; 82295496Sume sin4->sin_port = port; 82395496Sume sin4->sin_family = AF_INET; 82495496Sume sin4->sin_len = sizeof(struct sockaddr_in); 82594443Sume} 82694443Sume 82784047Sobrien/* 82884047Sobrien * Send an oack packet (option acknowledgement). 82984047Sobrien */ 83084047Sobrienstatic void 83190333Simpoack(void) 83284047Sobrien{ 83384047Sobrien struct tftphdr *tp, *ap; 83484047Sobrien int size, i, n; 83584047Sobrien char *bp; 83684047Sobrien 83784047Sobrien tp = (struct tftphdr *)buf; 83884047Sobrien bp = buf + 2; 83984047Sobrien size = sizeof(buf) - 2; 84084047Sobrien tp->th_opcode = htons((u_short)OACK); 84184047Sobrien for (i = 0; options[i].o_type != NULL; i++) { 84284047Sobrien if (options[i].o_request) { 84384047Sobrien n = snprintf(bp, size, "%s%c%d", options[i].o_type, 84484047Sobrien 0, options[i].o_reply); 84584047Sobrien bp += n+1; 84684047Sobrien size -= n+1; 84784047Sobrien if (size < 0) { 84884047Sobrien syslog(LOG_ERR, "oack: buffer overflow"); 84984047Sobrien exit(1); 85084047Sobrien } 85184047Sobrien } 85284047Sobrien } 85384047Sobrien size = bp - buf; 85484047Sobrien ap = (struct tftphdr *)ackbuf; 85584047Sobrien signal(SIGALRM, timer); 85684047Sobrien timeouts = 0; 85784047Sobrien 85884047Sobrien (void)setjmp(timeoutbuf); 85984047Sobrien if (send(peer, buf, size, 0) != size) { 86084047Sobrien syslog(LOG_INFO, "oack: %m"); 86184047Sobrien exit(1); 86284047Sobrien } 86384047Sobrien 86484047Sobrien for (;;) { 86584047Sobrien alarm(rexmtval); 86684047Sobrien n = recv(peer, ackbuf, sizeof (ackbuf), 0); 86784047Sobrien alarm(0); 86884047Sobrien if (n < 0) { 86984047Sobrien syslog(LOG_ERR, "recv: %m"); 87084047Sobrien exit(1); 87184047Sobrien } 87284047Sobrien ap->th_opcode = ntohs((u_short)ap->th_opcode); 87384047Sobrien ap->th_block = ntohs((u_short)ap->th_block); 87484047Sobrien if (ap->th_opcode == ERROR) 87584047Sobrien exit(1); 87684047Sobrien if (ap->th_opcode == ACK && ap->th_block == 0) 87784047Sobrien break; 87884047Sobrien } 87984047Sobrien} 880