tftpd.c revision 173852
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 173852 2007-11-23 00:05:29Z edwin $"; 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; 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; 112129680Smdoddstatic int create_new = 0; 113173852Sedwinstatic char *newfile_format = "%Y%m%d"; 114173852Sedwinstatic int increase_name = 0; 115129680Smdoddstatic mode_t mask = S_IWGRP|S_IWOTH; 1161592Srgrimes 117112452Sdwmalonestatic const char *errtomsg(int); 11890333Simpstatic void nak(int); 119112452Sdwmalonestatic void oack(void); 1201592Srgrimes 121112452Sdwmalonestatic void timer(int); 122112452Sdwmalonestatic void justquit(int); 123112452Sdwmalone 1241592Srgrimesint 12590333Simpmain(int argc, char *argv[]) 1261592Srgrimes{ 12790333Simp struct tftphdr *tp; 128141922Sstefanf socklen_t fromlen, len; 12990333Simp int n; 1301592Srgrimes int ch, on; 13194443Sume struct sockaddr_storage me; 13218458Simp char *chroot_dir = NULL; 13318458Simp struct passwd *nobody; 134112452Sdwmalone const char *chuser = "nobody"; 1351592Srgrimes 136130839Sbrian tzset(); /* syslog in localtime */ 137130839Sbrian 13835152Sphk openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 139173852Sedwin while ((ch = getopt(argc, argv, "cCF:lns:u:U:wW")) != -1) { 1401592Srgrimes switch (ch) { 14171616Sbillf case 'c': 14271616Sbillf ipchroot = 1; 14371616Sbillf break; 14471616Sbillf case 'C': 14571616Sbillf ipchroot = 2; 14671616Sbillf break; 147173852Sedwin case 'F': 148173852Sedwin newfile_format = optarg; 149173852Sedwin break; 1501592Srgrimes case 'l': 1511592Srgrimes logging = 1; 1521592Srgrimes break; 1531592Srgrimes case 'n': 1541592Srgrimes suppress_naks = 1; 1551592Srgrimes break; 15618458Simp case 's': 15718458Simp chroot_dir = optarg; 15818458Simp break; 15965850Swollman case 'u': 16065850Swollman chuser = optarg; 16165850Swollman break; 162129680Smdodd case 'U': 163129680Smdodd mask = strtol(optarg, NULL, 0); 164129680Smdodd break; 165129680Smdodd case 'w': 166129680Smdodd create_new = 1; 167129680Smdodd break; 168173852Sedwin case 'W': 169173852Sedwin create_new = 1; 170173852Sedwin increase_name = 1; 171173852Sedwin break; 1721592Srgrimes default: 1731592Srgrimes syslog(LOG_WARNING, "ignoring unknown option -%c", ch); 1741592Srgrimes } 1751592Srgrimes } 1761592Srgrimes if (optind < argc) { 1771592Srgrimes struct dirlist *dirp; 1781592Srgrimes 1791592Srgrimes /* Get list of directory prefixes. Skip relative pathnames. */ 1801592Srgrimes for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; 1811592Srgrimes optind++) { 1821592Srgrimes if (argv[optind][0] == '/') { 1831592Srgrimes dirp->name = argv[optind]; 1841592Srgrimes dirp->len = strlen(dirp->name); 1851592Srgrimes dirp++; 1861592Srgrimes } 1871592Srgrimes } 1881592Srgrimes } 18918458Simp else if (chroot_dir) { 19018458Simp dirs->name = "/"; 19118458Simp dirs->len = 1; 19218458Simp } 193113714Sbillf if (ipchroot > 0 && chroot_dir == NULL) { 19471616Sbillf syslog(LOG_ERR, "-c requires -s"); 19571616Sbillf exit(1); 19671616Sbillf } 1971592Srgrimes 198129680Smdodd umask(mask); 199129680Smdodd 2001592Srgrimes on = 1; 2011592Srgrimes if (ioctl(0, FIONBIO, &on) < 0) { 20231512Scharnier syslog(LOG_ERR, "ioctl(FIONBIO): %m"); 2031592Srgrimes exit(1); 2041592Srgrimes } 2051592Srgrimes fromlen = sizeof (from); 2061592Srgrimes n = recvfrom(0, buf, sizeof (buf), 0, 2071592Srgrimes (struct sockaddr *)&from, &fromlen); 2081592Srgrimes if (n < 0) { 20931512Scharnier syslog(LOG_ERR, "recvfrom: %m"); 2101592Srgrimes exit(1); 2111592Srgrimes } 2121592Srgrimes /* 2131592Srgrimes * Now that we have read the message out of the UDP 2141592Srgrimes * socket, we fork and exit. Thus, inetd will go back 2151592Srgrimes * to listening to the tftp port, and the next request 2161592Srgrimes * to come in will start up a new instance of tftpd. 2171592Srgrimes * 2181592Srgrimes * We do this so that inetd can run tftpd in "wait" mode. 2191592Srgrimes * The problem with tftpd running in "nowait" mode is that 2201592Srgrimes * inetd may get one or more successful "selects" on the 2211592Srgrimes * tftp port before we do our receive, so more than one 2221592Srgrimes * instance of tftpd may be started up. Worse, if tftpd 2231592Srgrimes * break before doing the above "recvfrom", inetd would 2241592Srgrimes * spawn endless instances, clogging the system. 2251592Srgrimes */ 2261592Srgrimes { 227141922Sstefanf int i, pid; 2281592Srgrimes 2291592Srgrimes for (i = 1; i < 20; i++) { 2301592Srgrimes pid = fork(); 2311592Srgrimes if (pid < 0) { 2321592Srgrimes sleep(i); 2331592Srgrimes /* 2341592Srgrimes * flush out to most recently sent request. 2351592Srgrimes * 2361592Srgrimes * This may drop some request, but those 2371592Srgrimes * will be resent by the clients when 2381592Srgrimes * they timeout. The positive effect of 2391592Srgrimes * this flush is to (try to) prevent more 2401592Srgrimes * than one tftpd being started up to service 2411592Srgrimes * a single request from a single client. 2421592Srgrimes */ 243141922Sstefanf fromlen = sizeof from; 2441592Srgrimes i = recvfrom(0, buf, sizeof (buf), 0, 245141922Sstefanf (struct sockaddr *)&from, &fromlen); 2461592Srgrimes if (i > 0) { 2471592Srgrimes n = i; 2481592Srgrimes } 2491592Srgrimes } else { 2501592Srgrimes break; 2511592Srgrimes } 2521592Srgrimes } 2531592Srgrimes if (pid < 0) { 25431512Scharnier syslog(LOG_ERR, "fork: %m"); 2551592Srgrimes exit(1); 2561592Srgrimes } else if (pid != 0) { 2571592Srgrimes exit(0); 2581592Srgrimes } 2591592Srgrimes } 26018458Simp 26118458Simp /* 26218458Simp * Since we exit here, we should do that only after the above 26318458Simp * recvfrom to keep inetd from constantly forking should there 26418458Simp * be a problem. See the above comment about system clogging. 26518458Simp */ 26618458Simp if (chroot_dir) { 267113714Sbillf if (ipchroot > 0) { 26871616Sbillf char *tempchroot; 26971616Sbillf struct stat sb; 27071616Sbillf int statret; 27194443Sume struct sockaddr_storage ss; 27294443Sume char hbuf[NI_MAXHOST]; 27371616Sbillf 27494443Sume memcpy(&ss, &from, from.ss_len); 27594443Sume unmappedaddr((struct sockaddr_in6 *)&ss); 27694443Sume getnameinfo((struct sockaddr *)&ss, ss.ss_len, 27794443Sume hbuf, sizeof(hbuf), NULL, 0, 278146187Sume NI_NUMERICHOST); 27994443Sume asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); 280113714Sbillf if (ipchroot == 2) 281113714Sbillf statret = stat(tempchroot, &sb); 282113714Sbillf if (ipchroot == 1 || 283113714Sbillf (statret == 0 && (sb.st_mode & S_IFDIR))) 28471616Sbillf chroot_dir = tempchroot; 28571616Sbillf } 28618458Simp /* Must get this before chroot because /etc might go away */ 28765850Swollman if ((nobody = getpwnam(chuser)) == NULL) { 28865850Swollman syslog(LOG_ERR, "%s: no such user", chuser); 28918458Simp exit(1); 29018458Simp } 29118458Simp if (chroot(chroot_dir)) { 29218458Simp syslog(LOG_ERR, "chroot: %s: %m", chroot_dir); 29318458Simp exit(1); 29418458Simp } 295131358Scsjp chdir("/"); 296131358Scsjp setgroups(1, &nobody->pw_gid); 29718458Simp setuid(nobody->pw_uid); 29818458Simp } 29918458Simp 30094443Sume len = sizeof(me); 30194443Sume if (getsockname(0, (struct sockaddr *)&me, &len) == 0) { 30294443Sume switch (me.ss_family) { 30394443Sume case AF_INET: 30494443Sume ((struct sockaddr_in *)&me)->sin_port = 0; 30594443Sume break; 30694443Sume case AF_INET6: 30794443Sume ((struct sockaddr_in6 *)&me)->sin6_port = 0; 30894443Sume break; 30994443Sume default: 31094443Sume /* unsupported */ 31194443Sume break; 31294443Sume } 31394443Sume } else { 31494443Sume memset(&me, 0, sizeof(me)); 31594443Sume me.ss_family = from.ss_family; 31694443Sume me.ss_len = from.ss_len; 31794443Sume } 3181592Srgrimes alarm(0); 3191592Srgrimes close(0); 3201592Srgrimes close(1); 32194443Sume peer = socket(from.ss_family, SOCK_DGRAM, 0); 3221592Srgrimes if (peer < 0) { 32331512Scharnier syslog(LOG_ERR, "socket: %m"); 3241592Srgrimes exit(1); 3251592Srgrimes } 32694443Sume if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) { 32731512Scharnier syslog(LOG_ERR, "bind: %m"); 3281592Srgrimes exit(1); 3291592Srgrimes } 33094443Sume if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) { 33131512Scharnier syslog(LOG_ERR, "connect: %m"); 3321592Srgrimes exit(1); 3331592Srgrimes } 3341592Srgrimes tp = (struct tftphdr *)buf; 3351592Srgrimes tp->th_opcode = ntohs(tp->th_opcode); 3361592Srgrimes if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 3371592Srgrimes tftp(tp, n); 3381592Srgrimes exit(1); 3391592Srgrimes} 3401592Srgrimes 341130834Sbrianstatic void 342130834Sbrianreduce_path(char *fn) 343130834Sbrian{ 344130834Sbrian char *slash, *ptr; 345130834Sbrian 346130834Sbrian /* Reduce all "/+./" to "/" (just in case we've got "/./../" later */ 347130834Sbrian while ((slash = strstr(fn, "/./")) != NULL) { 348130834Sbrian for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 349130834Sbrian ; 350130834Sbrian slash += 2; 351130834Sbrian while (*slash) 352130834Sbrian *++ptr = *++slash; 353130834Sbrian } 354130834Sbrian 355130834Sbrian /* Now reduce all "/something/+../" to "/" */ 356130834Sbrian while ((slash = strstr(fn, "/../")) != NULL) { 357130834Sbrian if (slash == fn) 358130834Sbrian break; 359130834Sbrian for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 360130834Sbrian ; 361130834Sbrian for (ptr--; ptr >= fn; ptr--) 362130834Sbrian if (*ptr == '/') 363130834Sbrian break; 364130834Sbrian if (ptr < fn) 365130834Sbrian break; 366130834Sbrian slash += 3; 367130834Sbrian while (*slash) 368130834Sbrian *++ptr = *++slash; 369130834Sbrian } 370130834Sbrian} 371130834Sbrian 3721592Srgrimesstruct formats; 37390333Simpint validate_access(char **, int); 37490333Simpvoid xmitfile(struct formats *); 37590333Simpvoid recvfile(struct formats *); 3761592Srgrimes 3771592Srgrimesstruct formats { 378112452Sdwmalone const char *f_mode; 37990333Simp int (*f_validate)(char **, int); 38090333Simp void (*f_send)(struct formats *); 38190333Simp void (*f_recv)(struct formats *); 3821592Srgrimes int f_convert; 3831592Srgrimes} formats[] = { 38440765Sdg { "netascii", validate_access, xmitfile, recvfile, 1 }, 38540765Sdg { "octet", validate_access, xmitfile, recvfile, 0 }, 3861592Srgrimes#ifdef notdef 3871592Srgrimes { "mail", validate_user, sendmail, recvmail, 1 }, 3881592Srgrimes#endif 389112452Sdwmalone { 0, NULL, NULL, NULL, 0 } 3901592Srgrimes}; 3911592Srgrimes 39284047Sobrienstruct options { 393112452Sdwmalone const char *o_type; 39484047Sobrien char *o_request; 39584047Sobrien int o_reply; /* turn into union if need be */ 39684047Sobrien} options[] = { 397112452Sdwmalone { "tsize", NULL, 0 }, /* OPT_TSIZE */ 398112452Sdwmalone { "timeout", NULL, 0 }, /* OPT_TIMEOUT */ 399112452Sdwmalone { NULL, NULL, 0 } 40084047Sobrien}; 40184047Sobrien 40284047Sobrienenum opt_enum { 40384047Sobrien OPT_TSIZE = 0, 40484047Sobrien OPT_TIMEOUT, 40584047Sobrien}; 40684047Sobrien 4071592Srgrimes/* 4081592Srgrimes * Handle initial connection protocol. 4091592Srgrimes */ 4101592Srgrimesvoid 41190333Simptftp(struct tftphdr *tp, int size) 4121592Srgrimes{ 41390333Simp char *cp; 41484047Sobrien int i, first = 1, has_options = 0, ecode; 41590333Simp struct formats *pf; 41684047Sobrien char *filename, *mode, *option, *ccp; 417141922Sstefanf char fnbuf[PATH_MAX]; 4181592Srgrimes 419122916Ssobomax cp = tp->th_stuff; 4201592Srgrimesagain: 4211592Srgrimes while (cp < buf + size) { 4221592Srgrimes if (*cp == '\0') 4231592Srgrimes break; 4241592Srgrimes cp++; 4251592Srgrimes } 4261592Srgrimes if (*cp != '\0') { 4271592Srgrimes nak(EBADOP); 4281592Srgrimes exit(1); 4291592Srgrimes } 430122916Ssobomax i = cp - tp->th_stuff; 431122916Ssobomax if (i >= sizeof(fnbuf)) { 432122916Ssobomax nak(EBADOP); 433122916Ssobomax exit(1); 434122916Ssobomax } 435122916Ssobomax memcpy(fnbuf, tp->th_stuff, i); 436122916Ssobomax fnbuf[i] = '\0'; 437130834Sbrian reduce_path(fnbuf); 438122916Ssobomax filename = fnbuf; 4391592Srgrimes if (first) { 4401592Srgrimes mode = ++cp; 4411592Srgrimes first = 0; 4421592Srgrimes goto again; 4431592Srgrimes } 4441592Srgrimes for (cp = mode; *cp; cp++) 4451592Srgrimes if (isupper(*cp)) 4461592Srgrimes *cp = tolower(*cp); 4471592Srgrimes for (pf = formats; pf->f_mode; pf++) 4481592Srgrimes if (strcmp(pf->f_mode, mode) == 0) 4491592Srgrimes break; 4501592Srgrimes if (pf->f_mode == 0) { 4511592Srgrimes nak(EBADOP); 4521592Srgrimes exit(1); 4531592Srgrimes } 45484047Sobrien while (++cp < buf + size) { 45584047Sobrien for (i = 2, ccp = cp; i > 0; ccp++) { 45684047Sobrien if (ccp >= buf + size) { 45786765Sbenno /* 45886765Sbenno * Don't reject the request, just stop trying 45986765Sbenno * to parse the option and get on with it. 460133862Smarius * Some Apple Open Firmware versions have 46186765Sbenno * trailing garbage on the end of otherwise 46286765Sbenno * valid requests. 46386765Sbenno */ 46486765Sbenno goto option_fail; 46584047Sobrien } else if (*ccp == '\0') 46684047Sobrien i--; 46784047Sobrien } 46884047Sobrien for (option = cp; *cp; cp++) 46984047Sobrien if (isupper(*cp)) 47084047Sobrien *cp = tolower(*cp); 47184047Sobrien for (i = 0; options[i].o_type != NULL; i++) 47284047Sobrien if (strcmp(option, options[i].o_type) == 0) { 47384047Sobrien options[i].o_request = ++cp; 47484047Sobrien has_options = 1; 47584047Sobrien } 47684047Sobrien cp = ccp-1; 47784047Sobrien } 47884047Sobrien 47986765Sbennooption_fail: 48084047Sobrien if (options[OPT_TIMEOUT].o_request) { 48184047Sobrien int to = atoi(options[OPT_TIMEOUT].o_request); 48284047Sobrien if (to < 1 || to > 255) { 48384047Sobrien nak(EBADOP); 48484047Sobrien exit(1); 48584047Sobrien } 48684047Sobrien else if (to <= max_rexmtval) 48784047Sobrien options[OPT_TIMEOUT].o_reply = rexmtval = to; 48884047Sobrien else 48984047Sobrien options[OPT_TIMEOUT].o_request = NULL; 49084047Sobrien } 49184047Sobrien 4921592Srgrimes ecode = (*pf->f_validate)(&filename, tp->th_opcode); 493130834Sbrian if (has_options && ecode == 0) 49484047Sobrien oack(); 4951592Srgrimes if (logging) { 49694443Sume char hbuf[NI_MAXHOST]; 49745393Sbrian 49894443Sume getnameinfo((struct sockaddr *)&from, from.ss_len, 499146187Sume hbuf, sizeof(hbuf), NULL, 0, 0); 50094443Sume syslog(LOG_INFO, "%s: %s request for %s: %s", hbuf, 5011592Srgrimes tp->th_opcode == WRQ ? "write" : "read", 5021592Srgrimes filename, errtomsg(ecode)); 5031592Srgrimes } 5041592Srgrimes if (ecode) { 5051592Srgrimes /* 5061592Srgrimes * Avoid storms of naks to a RRQ broadcast for a relative 5071592Srgrimes * bootfile pathname from a diskless Sun. 5081592Srgrimes */ 5091592Srgrimes if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 5101592Srgrimes exit(0); 5111592Srgrimes nak(ecode); 5121592Srgrimes exit(1); 5131592Srgrimes } 5141592Srgrimes if (tp->th_opcode == WRQ) 5151592Srgrimes (*pf->f_recv)(pf); 5161592Srgrimes else 5171592Srgrimes (*pf->f_send)(pf); 5181592Srgrimes exit(0); 5191592Srgrimes} 5201592Srgrimes 5211592Srgrimes 5221592SrgrimesFILE *file; 5231592Srgrimes 5241592Srgrimes/* 525173852Sedwin * Find the next value for YYYYMMDD.nn when the file to be written should 526173852Sedwin * be unique. Due to the limitations of nn, we will fail if nn reaches 100. 527173852Sedwin * Besides, that is four updates per hour on a file, which is kind of 528173852Sedwin * execessive anyway. 529173852Sedwin */ 530173852Sedwinstatic int 531173852Sedwinfind_next_name(char *filename, int *fd) 532173852Sedwin{ 533173852Sedwin int i; 534173852Sedwin time_t tval; 535173852Sedwin size_t len; 536173852Sedwin struct tm lt; 537173852Sedwin char yyyymmdd[MAXPATHLEN]; 538173852Sedwin char newname[MAXPATHLEN]; 539173852Sedwin struct stat sb; 540173852Sedwin int ret; 541173852Sedwin 542173852Sedwin /* Create the YYYYMMDD part of the filename */ 543173852Sedwin time(&tval); 544173852Sedwin lt = *localtime(&tval); 545173852Sedwin len = strftime(yyyymmdd, sizeof(yyyymmdd), newfile_format, <); 546173852Sedwin if (len == 0) { 547173852Sedwin syslog(LOG_WARNING, 548173852Sedwin "Filename suffix too long (%d characters maximum)", 549173852Sedwin MAXPATHLEN); 550173852Sedwin return (EACCESS); 551173852Sedwin } 552173852Sedwin 553173852Sedwin /* Make sure the new filename is not too long */ 554173852Sedwin if (strlen(filename) > MAXPATHLEN - len - 5) { 555173852Sedwin syslog(LOG_WARNING, 556173852Sedwin "Filename too long (%d characters, %d maximum)", 557173852Sedwin strlen(filename), MAXPATHLEN - len - 5); 558173852Sedwin return (EACCESS); 559173852Sedwin } 560173852Sedwin 561173852Sedwin /* Find the first file which doesn't exist */ 562173852Sedwin for (i = 0; i < 100; i++) { 563173852Sedwin sprintf(newname, "%s.%s.%02d", filename, yyyymmdd, i); 564173852Sedwin *fd = open(newname, 565173852Sedwin O_WRONLY | O_CREAT | O_EXCL, 566173852Sedwin S_IRUSR | S_IWUSR | S_IRGRP | 567173852Sedwin S_IWGRP | S_IROTH | S_IWOTH); 568173852Sedwin if (*fd > 0) 569173852Sedwin return 0; 570173852Sedwin } 571173852Sedwin 572173852Sedwin return (EEXIST); 573173852Sedwin} 574173852Sedwin 575173852Sedwin/* 5761592Srgrimes * Validate file access. Since we 5771592Srgrimes * have no uid or gid, for now require 5781592Srgrimes * file to exist and be publicly 5791592Srgrimes * readable/writable. 5801592Srgrimes * If we were invoked with arguments 5811592Srgrimes * from inetd then the file must also be 5821592Srgrimes * in one of the given directory prefixes. 5831592Srgrimes * Note also, full path name must be 5841592Srgrimes * given as we have no login directory. 5851592Srgrimes */ 5861592Srgrimesint 58790333Simpvalidate_access(char **filep, int mode) 5881592Srgrimes{ 5891592Srgrimes struct stat stbuf; 5901592Srgrimes int fd; 591173852Sedwin int error; 5921592Srgrimes struct dirlist *dirp; 5931592Srgrimes static char pathname[MAXPATHLEN]; 5941592Srgrimes char *filename = *filep; 5951592Srgrimes 5961592Srgrimes /* 5971592Srgrimes * Prevent tricksters from getting around the directory restrictions 5981592Srgrimes */ 5991592Srgrimes if (strstr(filename, "/../")) 6001592Srgrimes return (EACCESS); 6011592Srgrimes 6021592Srgrimes if (*filename == '/') { 6031592Srgrimes /* 6041592Srgrimes * Allow the request if it's in one of the approved locations. 6051592Srgrimes * Special case: check the null prefix ("/") by looking 6061592Srgrimes * for length = 1 and relying on the arg. processing that 6071592Srgrimes * it's a /. 6081592Srgrimes */ 6091592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 6101592Srgrimes if (dirp->len == 1 || 6111592Srgrimes (!strncmp(filename, dirp->name, dirp->len) && 6121592Srgrimes filename[dirp->len] == '/')) 6131592Srgrimes break; 6141592Srgrimes } 6151592Srgrimes /* If directory list is empty, allow access to any file */ 6161592Srgrimes if (dirp->name == NULL && dirp != dirs) 6171592Srgrimes return (EACCESS); 6181592Srgrimes if (stat(filename, &stbuf) < 0) 6191592Srgrimes return (errno == ENOENT ? ENOTFOUND : EACCESS); 6201592Srgrimes if ((stbuf.st_mode & S_IFMT) != S_IFREG) 6211592Srgrimes return (ENOTFOUND); 6221592Srgrimes if (mode == RRQ) { 6231592Srgrimes if ((stbuf.st_mode & S_IROTH) == 0) 6241592Srgrimes return (EACCESS); 6251592Srgrimes } else { 6261592Srgrimes if ((stbuf.st_mode & S_IWOTH) == 0) 6271592Srgrimes return (EACCESS); 6281592Srgrimes } 6291592Srgrimes } else { 6301592Srgrimes int err; 6311592Srgrimes 6321592Srgrimes /* 6331592Srgrimes * Relative file name: search the approved locations for it. 6346750Sjkh * Don't allow write requests that avoid directory 6351592Srgrimes * restrictions. 6361592Srgrimes */ 6371592Srgrimes 6386750Sjkh if (!strncmp(filename, "../", 3)) 6391592Srgrimes return (EACCESS); 6401592Srgrimes 6411592Srgrimes /* 6421592Srgrimes * If the file exists in one of the directories and isn't 6431592Srgrimes * readable, continue looking. However, change the error code 6441592Srgrimes * to give an indication that the file exists. 6451592Srgrimes */ 6461592Srgrimes err = ENOTFOUND; 6471592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 64824193Simp snprintf(pathname, sizeof(pathname), "%s/%s", 64924193Simp dirp->name, filename); 6501592Srgrimes if (stat(pathname, &stbuf) == 0 && 6511592Srgrimes (stbuf.st_mode & S_IFMT) == S_IFREG) { 6521592Srgrimes if ((stbuf.st_mode & S_IROTH) != 0) { 6531592Srgrimes break; 6541592Srgrimes } 6551592Srgrimes err = EACCESS; 6561592Srgrimes } 6571592Srgrimes } 658129680Smdodd if (dirp->name != NULL) 659129680Smdodd *filep = filename = pathname; 660129680Smdodd else if (mode == RRQ) 6611592Srgrimes return (err); 6621592Srgrimes } 66384047Sobrien if (options[OPT_TSIZE].o_request) { 66484047Sobrien if (mode == RRQ) 66584047Sobrien options[OPT_TSIZE].o_reply = stbuf.st_size; 66684047Sobrien else 66784047Sobrien /* XXX Allows writes of all sizes. */ 66884047Sobrien options[OPT_TSIZE].o_reply = 66984047Sobrien atoi(options[OPT_TSIZE].o_request); 67084047Sobrien } 671129680Smdodd if (mode == RRQ) 672129680Smdodd fd = open(filename, O_RDONLY); 673129680Smdodd else { 674173852Sedwin if (create_new) { 675173852Sedwin if (increase_name) { 676173852Sedwin error = find_next_name(filename, &fd); 677173852Sedwin if (error > 0) 678173852Sedwin return (error + 100); 679173852Sedwin } else 680173852Sedwin fd = open(filename, 681173852Sedwin O_WRONLY | O_TRUNC | O_CREAT, 682173852Sedwin S_IRUSR | S_IWUSR | S_IRGRP | 683173852Sedwin S_IWGRP | S_IROTH | S_IWOTH ); 684173852Sedwin } else 685173852Sedwin fd = open(filename, O_WRONLY | O_TRUNC); 686129680Smdodd } 6871592Srgrimes if (fd < 0) 6881592Srgrimes return (errno + 100); 6891592Srgrimes file = fdopen(fd, (mode == RRQ)? "r":"w"); 6901592Srgrimes if (file == NULL) { 691129683Smdodd close(fd); 692129683Smdodd return (errno + 100); 6931592Srgrimes } 6941592Srgrimes return (0); 6951592Srgrimes} 6961592Srgrimes 69784047Sobrienint timeouts; 6981592Srgrimesjmp_buf timeoutbuf; 6991592Srgrimes 7001592Srgrimesvoid 70190333Simptimer(int sig __unused) 7021592Srgrimes{ 70384047Sobrien if (++timeouts > MAX_TIMEOUTS) 7041592Srgrimes exit(1); 7051592Srgrimes longjmp(timeoutbuf, 1); 7061592Srgrimes} 7071592Srgrimes 7081592Srgrimes/* 7091592Srgrimes * Send the requested file. 7101592Srgrimes */ 7111592Srgrimesvoid 71290333Simpxmitfile(struct formats *pf) 7131592Srgrimes{ 714112452Sdwmalone struct tftphdr *dp; 71590333Simp struct tftphdr *ap; /* ack packet */ 71690333Simp int size, n; 71771926Sasmodai volatile unsigned short block; 7181592Srgrimes 7191592Srgrimes signal(SIGALRM, timer); 7201592Srgrimes dp = r_init(); 7211592Srgrimes ap = (struct tftphdr *)ackbuf; 7221592Srgrimes block = 1; 7231592Srgrimes do { 7241592Srgrimes size = readit(file, &dp, pf->f_convert); 7251592Srgrimes if (size < 0) { 7261592Srgrimes nak(errno + 100); 7271592Srgrimes goto abort; 7281592Srgrimes } 7291592Srgrimes dp->th_opcode = htons((u_short)DATA); 7301592Srgrimes dp->th_block = htons((u_short)block); 73184047Sobrien timeouts = 0; 7321592Srgrimes (void)setjmp(timeoutbuf); 7331592Srgrimes 7341592Srgrimessend_data: 73594299Sambrisko { 73694299Sambrisko int i, t = 1; 73794299Sambrisko for (i = 0; ; i++){ 73894299Sambrisko if (send(peer, dp, size + 4, 0) != size + 4) { 73994299Sambrisko sleep(t); 74094299Sambrisko t = (t < 32) ? t<< 1 : t; 74194299Sambrisko if (i >= 12) { 74294299Sambrisko syslog(LOG_ERR, "write: %m"); 74394299Sambrisko goto abort; 74494299Sambrisko } 74594299Sambrisko } 74694299Sambrisko break; 74794299Sambrisko } 7481592Srgrimes } 7491592Srgrimes read_ahead(file, pf->f_convert); 7501592Srgrimes for ( ; ; ) { 7511592Srgrimes alarm(rexmtval); /* read the ack */ 7521592Srgrimes n = recv(peer, ackbuf, sizeof (ackbuf), 0); 7531592Srgrimes alarm(0); 7541592Srgrimes if (n < 0) { 75531512Scharnier syslog(LOG_ERR, "read: %m"); 7561592Srgrimes goto abort; 7571592Srgrimes } 7581592Srgrimes ap->th_opcode = ntohs((u_short)ap->th_opcode); 7591592Srgrimes ap->th_block = ntohs((u_short)ap->th_block); 7601592Srgrimes 7611592Srgrimes if (ap->th_opcode == ERROR) 7621592Srgrimes goto abort; 7631592Srgrimes 7641592Srgrimes if (ap->th_opcode == ACK) { 7651592Srgrimes if (ap->th_block == block) 7661592Srgrimes break; 7671592Srgrimes /* Re-synchronize with the other side */ 7681592Srgrimes (void) synchnet(peer); 7691592Srgrimes if (ap->th_block == (block -1)) 7701592Srgrimes goto send_data; 7711592Srgrimes } 7721592Srgrimes 7731592Srgrimes } 7741592Srgrimes block++; 7751592Srgrimes } while (size == SEGSIZE); 7761592Srgrimesabort: 7771592Srgrimes (void) fclose(file); 7781592Srgrimes} 7791592Srgrimes 7801592Srgrimesvoid 78190333Simpjustquit(int sig __unused) 7821592Srgrimes{ 7831592Srgrimes exit(0); 7841592Srgrimes} 7851592Srgrimes 7861592Srgrimes 7871592Srgrimes/* 7881592Srgrimes * Receive a file. 7891592Srgrimes */ 7901592Srgrimesvoid 79190333Simprecvfile(struct formats *pf) 7921592Srgrimes{ 793112452Sdwmalone struct tftphdr *dp; 79490333Simp struct tftphdr *ap; /* ack buffer */ 79590333Simp int n, size; 79671926Sasmodai volatile unsigned short block; 7971592Srgrimes 7981592Srgrimes signal(SIGALRM, timer); 7991592Srgrimes dp = w_init(); 8001592Srgrimes ap = (struct tftphdr *)ackbuf; 8011592Srgrimes block = 0; 8021592Srgrimes do { 80384047Sobrien timeouts = 0; 8041592Srgrimes ap->th_opcode = htons((u_short)ACK); 8051592Srgrimes ap->th_block = htons((u_short)block); 8061592Srgrimes block++; 8071592Srgrimes (void) setjmp(timeoutbuf); 8081592Srgrimessend_ack: 8091592Srgrimes if (send(peer, ackbuf, 4, 0) != 4) { 81031512Scharnier syslog(LOG_ERR, "write: %m"); 8111592Srgrimes goto abort; 8121592Srgrimes } 8131592Srgrimes write_behind(file, pf->f_convert); 8141592Srgrimes for ( ; ; ) { 8151592Srgrimes alarm(rexmtval); 8161592Srgrimes n = recv(peer, dp, PKTSIZE, 0); 8171592Srgrimes alarm(0); 8181592Srgrimes if (n < 0) { /* really? */ 81931512Scharnier syslog(LOG_ERR, "read: %m"); 8201592Srgrimes goto abort; 8211592Srgrimes } 8221592Srgrimes dp->th_opcode = ntohs((u_short)dp->th_opcode); 8231592Srgrimes dp->th_block = ntohs((u_short)dp->th_block); 8241592Srgrimes if (dp->th_opcode == ERROR) 8251592Srgrimes goto abort; 8261592Srgrimes if (dp->th_opcode == DATA) { 8271592Srgrimes if (dp->th_block == block) { 8281592Srgrimes break; /* normal */ 8291592Srgrimes } 8301592Srgrimes /* Re-synchronize with the other side */ 8311592Srgrimes (void) synchnet(peer); 8321592Srgrimes if (dp->th_block == (block-1)) 8331592Srgrimes goto send_ack; /* rexmit */ 8341592Srgrimes } 8351592Srgrimes } 8361592Srgrimes /* size = write(file, dp->th_data, n - 4); */ 8371592Srgrimes size = writeit(file, &dp, n - 4, pf->f_convert); 8381592Srgrimes if (size != (n-4)) { /* ahem */ 8391592Srgrimes if (size < 0) nak(errno + 100); 8401592Srgrimes else nak(ENOSPACE); 8411592Srgrimes goto abort; 8421592Srgrimes } 8431592Srgrimes } while (size == SEGSIZE); 8441592Srgrimes write_behind(file, pf->f_convert); 8451592Srgrimes (void) fclose(file); /* close data file */ 8461592Srgrimes 8471592Srgrimes ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 8481592Srgrimes ap->th_block = htons((u_short)(block)); 8491592Srgrimes (void) send(peer, ackbuf, 4, 0); 8501592Srgrimes 8511592Srgrimes signal(SIGALRM, justquit); /* just quit on timeout */ 8521592Srgrimes alarm(rexmtval); 8531592Srgrimes n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 8541592Srgrimes alarm(0); 8551592Srgrimes if (n >= 4 && /* if read some data */ 8561592Srgrimes dp->th_opcode == DATA && /* and got a data block */ 8571592Srgrimes block == dp->th_block) { /* then my last ack was lost */ 8581592Srgrimes (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 8591592Srgrimes } 8601592Srgrimesabort: 8611592Srgrimes return; 8621592Srgrimes} 8631592Srgrimes 8641592Srgrimesstruct errmsg { 8651592Srgrimes int e_code; 866112452Sdwmalone const char *e_msg; 8671592Srgrimes} errmsgs[] = { 8681592Srgrimes { EUNDEF, "Undefined error code" }, 8691592Srgrimes { ENOTFOUND, "File not found" }, 8701592Srgrimes { EACCESS, "Access violation" }, 8711592Srgrimes { ENOSPACE, "Disk full or allocation exceeded" }, 8721592Srgrimes { EBADOP, "Illegal TFTP operation" }, 8731592Srgrimes { EBADID, "Unknown transfer ID" }, 8741592Srgrimes { EEXISTS, "File already exists" }, 8751592Srgrimes { ENOUSER, "No such user" }, 87684047Sobrien { EOPTNEG, "Option negotiation" }, 8771592Srgrimes { -1, 0 } 8781592Srgrimes}; 8791592Srgrimes 880112452Sdwmalonestatic const char * 88190333Simperrtomsg(int error) 8821592Srgrimes{ 883112452Sdwmalone static char ebuf[20]; 88490333Simp struct errmsg *pe; 8851592Srgrimes if (error == 0) 8861592Srgrimes return "success"; 8871592Srgrimes for (pe = errmsgs; pe->e_code >= 0; pe++) 8881592Srgrimes if (pe->e_code == error) 8891592Srgrimes return pe->e_msg; 890112452Sdwmalone snprintf(ebuf, sizeof(buf), "error %d", error); 891112452Sdwmalone return ebuf; 8921592Srgrimes} 8931592Srgrimes 8941592Srgrimes/* 8951592Srgrimes * Send a nak packet (error message). 8961592Srgrimes * Error code passed in is one of the 8971592Srgrimes * standard TFTP codes, or a UNIX errno 8981592Srgrimes * offset by 100. 8991592Srgrimes */ 9001592Srgrimesstatic void 90190333Simpnak(int error) 9021592Srgrimes{ 90390333Simp struct tftphdr *tp; 9041592Srgrimes int length; 90590333Simp struct errmsg *pe; 9061592Srgrimes 9071592Srgrimes tp = (struct tftphdr *)buf; 9081592Srgrimes tp->th_opcode = htons((u_short)ERROR); 9091592Srgrimes tp->th_code = htons((u_short)error); 9101592Srgrimes for (pe = errmsgs; pe->e_code >= 0; pe++) 9111592Srgrimes if (pe->e_code == error) 9121592Srgrimes break; 9131592Srgrimes if (pe->e_code < 0) { 9141592Srgrimes pe->e_msg = strerror(error - 100); 9151592Srgrimes tp->th_code = EUNDEF; /* set 'undef' errorcode */ 9161592Srgrimes } 9171592Srgrimes strcpy(tp->th_msg, pe->e_msg); 9181592Srgrimes length = strlen(pe->e_msg); 9191592Srgrimes tp->th_msg[length] = '\0'; 9201592Srgrimes length += 5; 9211592Srgrimes if (send(peer, buf, length, 0) != length) 92231512Scharnier syslog(LOG_ERR, "nak: %m"); 9231592Srgrimes} 92484047Sobrien 92594443Sume/* translate IPv4 mapped IPv6 address to IPv4 address */ 92694443Sumestatic void 92794443Sumeunmappedaddr(struct sockaddr_in6 *sin6) 92894443Sume{ 92995496Sume struct sockaddr_in *sin4; 93095496Sume u_int32_t addr; 93195496Sume int port; 93294443Sume 93395496Sume if (sin6->sin6_family != AF_INET6 || 93495496Sume !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 93595496Sume return; 93695496Sume sin4 = (struct sockaddr_in *)sin6; 93795496Sume addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 93895496Sume port = sin6->sin6_port; 93995496Sume memset(sin4, 0, sizeof(struct sockaddr_in)); 94095496Sume sin4->sin_addr.s_addr = addr; 94195496Sume sin4->sin_port = port; 94295496Sume sin4->sin_family = AF_INET; 94395496Sume sin4->sin_len = sizeof(struct sockaddr_in); 94494443Sume} 94594443Sume 94684047Sobrien/* 94784047Sobrien * Send an oack packet (option acknowledgement). 94884047Sobrien */ 94984047Sobrienstatic void 95090333Simpoack(void) 95184047Sobrien{ 95284047Sobrien struct tftphdr *tp, *ap; 95384047Sobrien int size, i, n; 95484047Sobrien char *bp; 95584047Sobrien 95684047Sobrien tp = (struct tftphdr *)buf; 95784047Sobrien bp = buf + 2; 95884047Sobrien size = sizeof(buf) - 2; 95984047Sobrien tp->th_opcode = htons((u_short)OACK); 96084047Sobrien for (i = 0; options[i].o_type != NULL; i++) { 96184047Sobrien if (options[i].o_request) { 96284047Sobrien n = snprintf(bp, size, "%s%c%d", options[i].o_type, 96384047Sobrien 0, options[i].o_reply); 96484047Sobrien bp += n+1; 96584047Sobrien size -= n+1; 96684047Sobrien if (size < 0) { 96784047Sobrien syslog(LOG_ERR, "oack: buffer overflow"); 96884047Sobrien exit(1); 96984047Sobrien } 97084047Sobrien } 97184047Sobrien } 97284047Sobrien size = bp - buf; 97384047Sobrien ap = (struct tftphdr *)ackbuf; 97484047Sobrien signal(SIGALRM, timer); 97584047Sobrien timeouts = 0; 97684047Sobrien 97784047Sobrien (void)setjmp(timeoutbuf); 97884047Sobrien if (send(peer, buf, size, 0) != size) { 97984047Sobrien syslog(LOG_INFO, "oack: %m"); 98084047Sobrien exit(1); 98184047Sobrien } 98284047Sobrien 98384047Sobrien for (;;) { 98484047Sobrien alarm(rexmtval); 98584047Sobrien n = recv(peer, ackbuf, sizeof (ackbuf), 0); 98684047Sobrien alarm(0); 98784047Sobrien if (n < 0) { 98884047Sobrien syslog(LOG_ERR, "recv: %m"); 98984047Sobrien exit(1); 99084047Sobrien } 99184047Sobrien ap->th_opcode = ntohs((u_short)ap->th_opcode); 99284047Sobrien ap->th_block = ntohs((u_short)ap->th_block); 99384047Sobrien if (ap->th_opcode == ERROR) 99484047Sobrien exit(1); 99584047Sobrien if (ap->th_opcode == ACK && ap->th_block == 0) 99684047Sobrien break; 99784047Sobrien } 99884047Sobrien} 999