tftpd.c revision 85299
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 85299 2001-10-22 01:55:40Z obrien $"; 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]; 911592Srgrimesstruct sockaddr_in from; 921592Srgrimesint fromlen; 931592Srgrimes 941592Srgrimesvoid tftp __P((struct tftphdr *, int)); 951592Srgrimes 961592Srgrimes/* 971592Srgrimes * Null-terminated directory prefix list for absolute pathname requests and 981592Srgrimes * search list for relative pathname requests. 991592Srgrimes * 1001592Srgrimes * MAXDIRS should be at least as large as the number of arguments that 1011592Srgrimes * inetd allows (currently 20). 1021592Srgrimes */ 1031592Srgrimes#define MAXDIRS 20 1041592Srgrimesstatic struct dirlist { 1051592Srgrimes char *name; 1061592Srgrimes int len; 1071592Srgrimes} dirs[MAXDIRS+1]; 1081592Srgrimesstatic int suppress_naks; 1091592Srgrimesstatic int logging; 11071616Sbillfstatic int ipchroot; 1111592Srgrimes 1121592Srgrimesstatic char *errtomsg __P((int)); 1131592Srgrimesstatic void nak __P((int)); 11484047Sobrienstatic void oack __P(()); 1151592Srgrimes 1161592Srgrimesint 1171592Srgrimesmain(argc, argv) 1181592Srgrimes int argc; 1191592Srgrimes char *argv[]; 1201592Srgrimes{ 1211592Srgrimes register struct tftphdr *tp; 1221592Srgrimes register int n; 1231592Srgrimes int ch, on; 1241592Srgrimes struct sockaddr_in sin; 12518458Simp char *chroot_dir = NULL; 12618458Simp struct passwd *nobody; 12765850Swollman char *chuser = "nobody"; 1281592Srgrimes 12935152Sphk openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 13071616Sbillf while ((ch = getopt(argc, argv, "cClns:u:")) != -1) { 1311592Srgrimes switch (ch) { 13271616Sbillf case 'c': 13371616Sbillf ipchroot = 1; 13471616Sbillf break; 13571616Sbillf case 'C': 13671616Sbillf ipchroot = 2; 13771616Sbillf break; 1381592Srgrimes case 'l': 1391592Srgrimes logging = 1; 1401592Srgrimes break; 1411592Srgrimes case 'n': 1421592Srgrimes suppress_naks = 1; 1431592Srgrimes break; 14418458Simp case 's': 14518458Simp chroot_dir = optarg; 14618458Simp break; 14765850Swollman case 'u': 14865850Swollman chuser = optarg; 14965850Swollman break; 1501592Srgrimes default: 1511592Srgrimes syslog(LOG_WARNING, "ignoring unknown option -%c", ch); 1521592Srgrimes } 1531592Srgrimes } 1541592Srgrimes if (optind < argc) { 1551592Srgrimes struct dirlist *dirp; 1561592Srgrimes 1571592Srgrimes /* Get list of directory prefixes. Skip relative pathnames. */ 1581592Srgrimes for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; 1591592Srgrimes optind++) { 1601592Srgrimes if (argv[optind][0] == '/') { 1611592Srgrimes dirp->name = argv[optind]; 1621592Srgrimes dirp->len = strlen(dirp->name); 1631592Srgrimes dirp++; 1641592Srgrimes } 1651592Srgrimes } 1661592Srgrimes } 16718458Simp else if (chroot_dir) { 16818458Simp dirs->name = "/"; 16918458Simp dirs->len = 1; 17018458Simp } 17171616Sbillf if (ipchroot && chroot_dir == NULL) { 17271616Sbillf syslog(LOG_ERR, "-c requires -s"); 17371616Sbillf exit(1); 17471616Sbillf } 1751592Srgrimes 1761592Srgrimes on = 1; 1771592Srgrimes if (ioctl(0, FIONBIO, &on) < 0) { 17831512Scharnier syslog(LOG_ERR, "ioctl(FIONBIO): %m"); 1791592Srgrimes exit(1); 1801592Srgrimes } 1811592Srgrimes fromlen = sizeof (from); 1821592Srgrimes n = recvfrom(0, buf, sizeof (buf), 0, 1831592Srgrimes (struct sockaddr *)&from, &fromlen); 1841592Srgrimes if (n < 0) { 18531512Scharnier syslog(LOG_ERR, "recvfrom: %m"); 1861592Srgrimes exit(1); 1871592Srgrimes } 1881592Srgrimes /* 1891592Srgrimes * Now that we have read the message out of the UDP 1901592Srgrimes * socket, we fork and exit. Thus, inetd will go back 1911592Srgrimes * to listening to the tftp port, and the next request 1921592Srgrimes * to come in will start up a new instance of tftpd. 1931592Srgrimes * 1941592Srgrimes * We do this so that inetd can run tftpd in "wait" mode. 1951592Srgrimes * The problem with tftpd running in "nowait" mode is that 1961592Srgrimes * inetd may get one or more successful "selects" on the 1971592Srgrimes * tftp port before we do our receive, so more than one 1981592Srgrimes * instance of tftpd may be started up. Worse, if tftpd 1991592Srgrimes * break before doing the above "recvfrom", inetd would 2001592Srgrimes * spawn endless instances, clogging the system. 2011592Srgrimes */ 2021592Srgrimes { 2031592Srgrimes int pid; 2041592Srgrimes int i, j; 2051592Srgrimes 2061592Srgrimes for (i = 1; i < 20; i++) { 2071592Srgrimes pid = fork(); 2081592Srgrimes if (pid < 0) { 2091592Srgrimes sleep(i); 2101592Srgrimes /* 2111592Srgrimes * flush out to most recently sent request. 2121592Srgrimes * 2131592Srgrimes * This may drop some request, but those 2141592Srgrimes * will be resent by the clients when 2151592Srgrimes * they timeout. The positive effect of 2161592Srgrimes * this flush is to (try to) prevent more 2171592Srgrimes * than one tftpd being started up to service 2181592Srgrimes * a single request from a single client. 2191592Srgrimes */ 2201592Srgrimes j = sizeof from; 2211592Srgrimes i = recvfrom(0, buf, sizeof (buf), 0, 2221592Srgrimes (struct sockaddr *)&from, &j); 2231592Srgrimes if (i > 0) { 2241592Srgrimes n = i; 2251592Srgrimes fromlen = j; 2261592Srgrimes } 2271592Srgrimes } else { 2281592Srgrimes break; 2291592Srgrimes } 2301592Srgrimes } 2311592Srgrimes if (pid < 0) { 23231512Scharnier syslog(LOG_ERR, "fork: %m"); 2331592Srgrimes exit(1); 2341592Srgrimes } else if (pid != 0) { 2351592Srgrimes exit(0); 2361592Srgrimes } 2371592Srgrimes } 23818458Simp 23918458Simp /* 24018458Simp * Since we exit here, we should do that only after the above 24118458Simp * recvfrom to keep inetd from constantly forking should there 24218458Simp * be a problem. See the above comment about system clogging. 24318458Simp */ 24418458Simp if (chroot_dir) { 24571616Sbillf if (ipchroot) { 24671616Sbillf char *tempchroot; 24771616Sbillf struct stat sb; 24871616Sbillf int statret; 24971616Sbillf 25071616Sbillf tempchroot = inet_ntoa(from.sin_addr); 25171616Sbillf asprintf(&tempchroot, "%s/%s", chroot_dir, tempchroot); 25271616Sbillf statret = stat(tempchroot, &sb); 25371616Sbillf if ((sb.st_mode & S_IFDIR) && 25471616Sbillf (statret == 0 || (statret == -1 && ipchroot == 1))) 25571616Sbillf chroot_dir = tempchroot; 25671616Sbillf } 25718458Simp /* Must get this before chroot because /etc might go away */ 25865850Swollman if ((nobody = getpwnam(chuser)) == NULL) { 25965850Swollman syslog(LOG_ERR, "%s: no such user", chuser); 26018458Simp exit(1); 26118458Simp } 26218458Simp if (chroot(chroot_dir)) { 26318458Simp syslog(LOG_ERR, "chroot: %s: %m", chroot_dir); 26418458Simp exit(1); 26518458Simp } 26618458Simp chdir( "/" ); 26718458Simp setuid(nobody->pw_uid); 26885299Sobrien setgroups(1, &nobody->pw_gid); 26918458Simp } 27018458Simp 2711592Srgrimes from.sin_family = AF_INET; 2721592Srgrimes alarm(0); 2731592Srgrimes close(0); 2741592Srgrimes close(1); 2751592Srgrimes peer = socket(AF_INET, SOCK_DGRAM, 0); 2761592Srgrimes if (peer < 0) { 27731512Scharnier syslog(LOG_ERR, "socket: %m"); 2781592Srgrimes exit(1); 2791592Srgrimes } 2801592Srgrimes memset(&sin, 0, sizeof(sin)); 2811592Srgrimes sin.sin_family = AF_INET; 2821592Srgrimes if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) { 28331512Scharnier syslog(LOG_ERR, "bind: %m"); 2841592Srgrimes exit(1); 2851592Srgrimes } 2861592Srgrimes if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) { 28731512Scharnier syslog(LOG_ERR, "connect: %m"); 2881592Srgrimes exit(1); 2891592Srgrimes } 2901592Srgrimes tp = (struct tftphdr *)buf; 2911592Srgrimes tp->th_opcode = ntohs(tp->th_opcode); 2921592Srgrimes if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 2931592Srgrimes tftp(tp, n); 2941592Srgrimes exit(1); 2951592Srgrimes} 2961592Srgrimes 2971592Srgrimesstruct formats; 2981592Srgrimesint validate_access __P((char **, int)); 29940765Sdgvoid xmitfile __P((struct formats *)); 3001592Srgrimesvoid recvfile __P((struct formats *)); 3011592Srgrimes 3021592Srgrimesstruct formats { 3031592Srgrimes char *f_mode; 3041592Srgrimes int (*f_validate) __P((char **, int)); 3051592Srgrimes void (*f_send) __P((struct formats *)); 3061592Srgrimes void (*f_recv) __P((struct formats *)); 3071592Srgrimes int f_convert; 3081592Srgrimes} formats[] = { 30940765Sdg { "netascii", validate_access, xmitfile, recvfile, 1 }, 31040765Sdg { "octet", validate_access, xmitfile, recvfile, 0 }, 3111592Srgrimes#ifdef notdef 3121592Srgrimes { "mail", validate_user, sendmail, recvmail, 1 }, 3131592Srgrimes#endif 3141592Srgrimes { 0 } 3151592Srgrimes}; 3161592Srgrimes 31784047Sobrienstruct options { 31884047Sobrien char *o_type; 31984047Sobrien char *o_request; 32084047Sobrien int o_reply; /* turn into union if need be */ 32184047Sobrien} options[] = { 32284047Sobrien { "tsize" }, /* OPT_TSIZE */ 32384047Sobrien { "timeout" }, /* OPT_TIMEOUT */ 32484047Sobrien { NULL } 32584047Sobrien}; 32684047Sobrien 32784047Sobrienenum opt_enum { 32884047Sobrien OPT_TSIZE = 0, 32984047Sobrien OPT_TIMEOUT, 33084047Sobrien}; 33184047Sobrien 3321592Srgrimes/* 3331592Srgrimes * Handle initial connection protocol. 3341592Srgrimes */ 3351592Srgrimesvoid 3361592Srgrimestftp(tp, size) 3371592Srgrimes struct tftphdr *tp; 3381592Srgrimes int size; 3391592Srgrimes{ 3401592Srgrimes register char *cp; 34184047Sobrien int i, first = 1, has_options = 0, ecode; 3421592Srgrimes register struct formats *pf; 34384047Sobrien char *filename, *mode, *option, *ccp; 3441592Srgrimes 3451592Srgrimes filename = cp = tp->th_stuff; 3461592Srgrimesagain: 3471592Srgrimes while (cp < buf + size) { 3481592Srgrimes if (*cp == '\0') 3491592Srgrimes break; 3501592Srgrimes cp++; 3511592Srgrimes } 3521592Srgrimes if (*cp != '\0') { 3531592Srgrimes nak(EBADOP); 3541592Srgrimes exit(1); 3551592Srgrimes } 3561592Srgrimes if (first) { 3571592Srgrimes mode = ++cp; 3581592Srgrimes first = 0; 3591592Srgrimes goto again; 3601592Srgrimes } 3611592Srgrimes for (cp = mode; *cp; cp++) 3621592Srgrimes if (isupper(*cp)) 3631592Srgrimes *cp = tolower(*cp); 3641592Srgrimes for (pf = formats; pf->f_mode; pf++) 3651592Srgrimes if (strcmp(pf->f_mode, mode) == 0) 3661592Srgrimes break; 3671592Srgrimes if (pf->f_mode == 0) { 3681592Srgrimes nak(EBADOP); 3691592Srgrimes exit(1); 3701592Srgrimes } 37184047Sobrien while (++cp < buf + size) { 37284047Sobrien for (i = 2, ccp = cp; i > 0; ccp++) { 37384047Sobrien if (ccp >= buf + size) { 37484047Sobrien nak(EBADOP); 37584047Sobrien exit(1); 37684047Sobrien } else if (*ccp == '\0') 37784047Sobrien i--; 37884047Sobrien } 37984047Sobrien for (option = cp; *cp; cp++) 38084047Sobrien if (isupper(*cp)) 38184047Sobrien *cp = tolower(*cp); 38284047Sobrien for (i = 0; options[i].o_type != NULL; i++) 38384047Sobrien if (strcmp(option, options[i].o_type) == 0) { 38484047Sobrien options[i].o_request = ++cp; 38584047Sobrien has_options = 1; 38684047Sobrien } 38784047Sobrien cp = ccp-1; 38884047Sobrien } 38984047Sobrien 39084047Sobrien if (options[OPT_TIMEOUT].o_request) { 39184047Sobrien int to = atoi(options[OPT_TIMEOUT].o_request); 39284047Sobrien if (to < 1 || to > 255) { 39384047Sobrien nak(EBADOP); 39484047Sobrien exit(1); 39584047Sobrien } 39684047Sobrien else if (to <= max_rexmtval) 39784047Sobrien options[OPT_TIMEOUT].o_reply = rexmtval = to; 39884047Sobrien else 39984047Sobrien options[OPT_TIMEOUT].o_request = NULL; 40084047Sobrien } 40184047Sobrien 4021592Srgrimes ecode = (*pf->f_validate)(&filename, tp->th_opcode); 40384047Sobrien if (has_options) 40484047Sobrien oack(); 4051592Srgrimes if (logging) { 40645422Sbrian char host[MAXHOSTNAMELEN]; 40745393Sbrian 40845422Sbrian realhostname(host, sizeof(host) - 1, &from.sin_addr); 40945422Sbrian host[sizeof(host) - 1] = '\0'; 41045393Sbrian syslog(LOG_INFO, "%s: %s request for %s: %s", host, 4111592Srgrimes tp->th_opcode == WRQ ? "write" : "read", 4121592Srgrimes filename, errtomsg(ecode)); 4131592Srgrimes } 4141592Srgrimes if (ecode) { 4151592Srgrimes /* 4161592Srgrimes * Avoid storms of naks to a RRQ broadcast for a relative 4171592Srgrimes * bootfile pathname from a diskless Sun. 4181592Srgrimes */ 4191592Srgrimes if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 4201592Srgrimes exit(0); 4211592Srgrimes nak(ecode); 4221592Srgrimes exit(1); 4231592Srgrimes } 4241592Srgrimes if (tp->th_opcode == WRQ) 4251592Srgrimes (*pf->f_recv)(pf); 4261592Srgrimes else 4271592Srgrimes (*pf->f_send)(pf); 4281592Srgrimes exit(0); 4291592Srgrimes} 4301592Srgrimes 4311592Srgrimes 4321592SrgrimesFILE *file; 4331592Srgrimes 4341592Srgrimes/* 4351592Srgrimes * Validate file access. Since we 4361592Srgrimes * have no uid or gid, for now require 4371592Srgrimes * file to exist and be publicly 4381592Srgrimes * readable/writable. 4391592Srgrimes * If we were invoked with arguments 4401592Srgrimes * from inetd then the file must also be 4411592Srgrimes * in one of the given directory prefixes. 4421592Srgrimes * Note also, full path name must be 4431592Srgrimes * given as we have no login directory. 4441592Srgrimes */ 4451592Srgrimesint 4461592Srgrimesvalidate_access(filep, mode) 4471592Srgrimes char **filep; 4481592Srgrimes int mode; 4491592Srgrimes{ 4501592Srgrimes struct stat stbuf; 4511592Srgrimes int fd; 4521592Srgrimes struct dirlist *dirp; 4531592Srgrimes static char pathname[MAXPATHLEN]; 4541592Srgrimes char *filename = *filep; 4551592Srgrimes 4561592Srgrimes /* 4571592Srgrimes * Prevent tricksters from getting around the directory restrictions 4581592Srgrimes */ 4591592Srgrimes if (strstr(filename, "/../")) 4601592Srgrimes return (EACCESS); 4611592Srgrimes 4621592Srgrimes if (*filename == '/') { 4631592Srgrimes /* 4641592Srgrimes * Allow the request if it's in one of the approved locations. 4651592Srgrimes * Special case: check the null prefix ("/") by looking 4661592Srgrimes * for length = 1 and relying on the arg. processing that 4671592Srgrimes * it's a /. 4681592Srgrimes */ 4691592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 4701592Srgrimes if (dirp->len == 1 || 4711592Srgrimes (!strncmp(filename, dirp->name, dirp->len) && 4721592Srgrimes filename[dirp->len] == '/')) 4731592Srgrimes break; 4741592Srgrimes } 4751592Srgrimes /* If directory list is empty, allow access to any file */ 4761592Srgrimes if (dirp->name == NULL && dirp != dirs) 4771592Srgrimes return (EACCESS); 4781592Srgrimes if (stat(filename, &stbuf) < 0) 4791592Srgrimes return (errno == ENOENT ? ENOTFOUND : EACCESS); 4801592Srgrimes if ((stbuf.st_mode & S_IFMT) != S_IFREG) 4811592Srgrimes return (ENOTFOUND); 4821592Srgrimes if (mode == RRQ) { 4831592Srgrimes if ((stbuf.st_mode & S_IROTH) == 0) 4841592Srgrimes return (EACCESS); 4851592Srgrimes } else { 4861592Srgrimes if ((stbuf.st_mode & S_IWOTH) == 0) 4871592Srgrimes return (EACCESS); 4881592Srgrimes } 4891592Srgrimes } else { 4901592Srgrimes int err; 4911592Srgrimes 4921592Srgrimes /* 4931592Srgrimes * Relative file name: search the approved locations for it. 4946750Sjkh * Don't allow write requests that avoid directory 4951592Srgrimes * restrictions. 4961592Srgrimes */ 4971592Srgrimes 4986750Sjkh if (!strncmp(filename, "../", 3)) 4991592Srgrimes return (EACCESS); 5001592Srgrimes 5011592Srgrimes /* 5021592Srgrimes * If the file exists in one of the directories and isn't 5031592Srgrimes * readable, continue looking. However, change the error code 5041592Srgrimes * to give an indication that the file exists. 5051592Srgrimes */ 5061592Srgrimes err = ENOTFOUND; 5071592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 50824193Simp snprintf(pathname, sizeof(pathname), "%s/%s", 50924193Simp dirp->name, filename); 5101592Srgrimes if (stat(pathname, &stbuf) == 0 && 5111592Srgrimes (stbuf.st_mode & S_IFMT) == S_IFREG) { 5121592Srgrimes if ((stbuf.st_mode & S_IROTH) != 0) { 5131592Srgrimes break; 5141592Srgrimes } 5151592Srgrimes err = EACCESS; 5161592Srgrimes } 5171592Srgrimes } 5181592Srgrimes if (dirp->name == NULL) 5191592Srgrimes return (err); 5201592Srgrimes *filep = filename = pathname; 5211592Srgrimes } 52284047Sobrien if (options[OPT_TSIZE].o_request) { 52384047Sobrien if (mode == RRQ) 52484047Sobrien options[OPT_TSIZE].o_reply = stbuf.st_size; 52584047Sobrien else 52684047Sobrien /* XXX Allows writes of all sizes. */ 52784047Sobrien options[OPT_TSIZE].o_reply = 52884047Sobrien atoi(options[OPT_TSIZE].o_request); 52984047Sobrien } 53020052Sjoerg fd = open(filename, mode == RRQ ? O_RDONLY : O_WRONLY|O_TRUNC); 5311592Srgrimes if (fd < 0) 5321592Srgrimes return (errno + 100); 5331592Srgrimes file = fdopen(fd, (mode == RRQ)? "r":"w"); 5341592Srgrimes if (file == NULL) { 5351592Srgrimes return errno+100; 5361592Srgrimes } 5371592Srgrimes return (0); 5381592Srgrimes} 5391592Srgrimes 54084047Sobrienint timeouts; 5411592Srgrimesjmp_buf timeoutbuf; 5421592Srgrimes 5431592Srgrimesvoid 5441592Srgrimestimer() 5451592Srgrimes{ 54684047Sobrien if (++timeouts > MAX_TIMEOUTS) 5471592Srgrimes exit(1); 5481592Srgrimes longjmp(timeoutbuf, 1); 5491592Srgrimes} 5501592Srgrimes 5511592Srgrimes/* 5521592Srgrimes * Send the requested file. 5531592Srgrimes */ 5541592Srgrimesvoid 55540765Sdgxmitfile(pf) 5561592Srgrimes struct formats *pf; 5571592Srgrimes{ 5581592Srgrimes struct tftphdr *dp, *r_init(); 5591592Srgrimes register struct tftphdr *ap; /* ack packet */ 5601592Srgrimes register int size, n; 56171926Sasmodai volatile unsigned short block; 5621592Srgrimes 5631592Srgrimes signal(SIGALRM, timer); 5641592Srgrimes dp = r_init(); 5651592Srgrimes ap = (struct tftphdr *)ackbuf; 5661592Srgrimes block = 1; 5671592Srgrimes do { 5681592Srgrimes size = readit(file, &dp, pf->f_convert); 5691592Srgrimes if (size < 0) { 5701592Srgrimes nak(errno + 100); 5711592Srgrimes goto abort; 5721592Srgrimes } 5731592Srgrimes dp->th_opcode = htons((u_short)DATA); 5741592Srgrimes dp->th_block = htons((u_short)block); 57584047Sobrien timeouts = 0; 5761592Srgrimes (void)setjmp(timeoutbuf); 5771592Srgrimes 5781592Srgrimessend_data: 5791592Srgrimes if (send(peer, dp, size + 4, 0) != size + 4) { 58031512Scharnier syslog(LOG_ERR, "write: %m"); 5811592Srgrimes goto abort; 5821592Srgrimes } 5831592Srgrimes read_ahead(file, pf->f_convert); 5841592Srgrimes for ( ; ; ) { 5851592Srgrimes alarm(rexmtval); /* read the ack */ 5861592Srgrimes n = recv(peer, ackbuf, sizeof (ackbuf), 0); 5871592Srgrimes alarm(0); 5881592Srgrimes if (n < 0) { 58931512Scharnier syslog(LOG_ERR, "read: %m"); 5901592Srgrimes goto abort; 5911592Srgrimes } 5921592Srgrimes ap->th_opcode = ntohs((u_short)ap->th_opcode); 5931592Srgrimes ap->th_block = ntohs((u_short)ap->th_block); 5941592Srgrimes 5951592Srgrimes if (ap->th_opcode == ERROR) 5961592Srgrimes goto abort; 5971592Srgrimes 5981592Srgrimes if (ap->th_opcode == ACK) { 5991592Srgrimes if (ap->th_block == block) 6001592Srgrimes break; 6011592Srgrimes /* Re-synchronize with the other side */ 6021592Srgrimes (void) synchnet(peer); 6031592Srgrimes if (ap->th_block == (block -1)) 6041592Srgrimes goto send_data; 6051592Srgrimes } 6061592Srgrimes 6071592Srgrimes } 6081592Srgrimes block++; 6091592Srgrimes } while (size == SEGSIZE); 6101592Srgrimesabort: 6111592Srgrimes (void) fclose(file); 6121592Srgrimes} 6131592Srgrimes 6141592Srgrimesvoid 6151592Srgrimesjustquit() 6161592Srgrimes{ 6171592Srgrimes exit(0); 6181592Srgrimes} 6191592Srgrimes 6201592Srgrimes 6211592Srgrimes/* 6221592Srgrimes * Receive a file. 6231592Srgrimes */ 6241592Srgrimesvoid 6251592Srgrimesrecvfile(pf) 6261592Srgrimes struct formats *pf; 6271592Srgrimes{ 6281592Srgrimes struct tftphdr *dp, *w_init(); 6291592Srgrimes register struct tftphdr *ap; /* ack buffer */ 6301592Srgrimes register int n, size; 63171926Sasmodai volatile unsigned short block; 6321592Srgrimes 6331592Srgrimes signal(SIGALRM, timer); 6341592Srgrimes dp = w_init(); 6351592Srgrimes ap = (struct tftphdr *)ackbuf; 6361592Srgrimes block = 0; 6371592Srgrimes do { 63884047Sobrien timeouts = 0; 6391592Srgrimes ap->th_opcode = htons((u_short)ACK); 6401592Srgrimes ap->th_block = htons((u_short)block); 6411592Srgrimes block++; 6421592Srgrimes (void) setjmp(timeoutbuf); 6431592Srgrimessend_ack: 6441592Srgrimes if (send(peer, ackbuf, 4, 0) != 4) { 64531512Scharnier syslog(LOG_ERR, "write: %m"); 6461592Srgrimes goto abort; 6471592Srgrimes } 6481592Srgrimes write_behind(file, pf->f_convert); 6491592Srgrimes for ( ; ; ) { 6501592Srgrimes alarm(rexmtval); 6511592Srgrimes n = recv(peer, dp, PKTSIZE, 0); 6521592Srgrimes alarm(0); 6531592Srgrimes if (n < 0) { /* really? */ 65431512Scharnier syslog(LOG_ERR, "read: %m"); 6551592Srgrimes goto abort; 6561592Srgrimes } 6571592Srgrimes dp->th_opcode = ntohs((u_short)dp->th_opcode); 6581592Srgrimes dp->th_block = ntohs((u_short)dp->th_block); 6591592Srgrimes if (dp->th_opcode == ERROR) 6601592Srgrimes goto abort; 6611592Srgrimes if (dp->th_opcode == DATA) { 6621592Srgrimes if (dp->th_block == block) { 6631592Srgrimes break; /* normal */ 6641592Srgrimes } 6651592Srgrimes /* Re-synchronize with the other side */ 6661592Srgrimes (void) synchnet(peer); 6671592Srgrimes if (dp->th_block == (block-1)) 6681592Srgrimes goto send_ack; /* rexmit */ 6691592Srgrimes } 6701592Srgrimes } 6711592Srgrimes /* size = write(file, dp->th_data, n - 4); */ 6721592Srgrimes size = writeit(file, &dp, n - 4, pf->f_convert); 6731592Srgrimes if (size != (n-4)) { /* ahem */ 6741592Srgrimes if (size < 0) nak(errno + 100); 6751592Srgrimes else nak(ENOSPACE); 6761592Srgrimes goto abort; 6771592Srgrimes } 6781592Srgrimes } while (size == SEGSIZE); 6791592Srgrimes write_behind(file, pf->f_convert); 6801592Srgrimes (void) fclose(file); /* close data file */ 6811592Srgrimes 6821592Srgrimes ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 6831592Srgrimes ap->th_block = htons((u_short)(block)); 6841592Srgrimes (void) send(peer, ackbuf, 4, 0); 6851592Srgrimes 6861592Srgrimes signal(SIGALRM, justquit); /* just quit on timeout */ 6871592Srgrimes alarm(rexmtval); 6881592Srgrimes n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 6891592Srgrimes alarm(0); 6901592Srgrimes if (n >= 4 && /* if read some data */ 6911592Srgrimes dp->th_opcode == DATA && /* and got a data block */ 6921592Srgrimes block == dp->th_block) { /* then my last ack was lost */ 6931592Srgrimes (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 6941592Srgrimes } 6951592Srgrimesabort: 6961592Srgrimes return; 6971592Srgrimes} 6981592Srgrimes 6991592Srgrimesstruct errmsg { 7001592Srgrimes int e_code; 7011592Srgrimes char *e_msg; 7021592Srgrimes} errmsgs[] = { 7031592Srgrimes { EUNDEF, "Undefined error code" }, 7041592Srgrimes { ENOTFOUND, "File not found" }, 7051592Srgrimes { EACCESS, "Access violation" }, 7061592Srgrimes { ENOSPACE, "Disk full or allocation exceeded" }, 7071592Srgrimes { EBADOP, "Illegal TFTP operation" }, 7081592Srgrimes { EBADID, "Unknown transfer ID" }, 7091592Srgrimes { EEXISTS, "File already exists" }, 7101592Srgrimes { ENOUSER, "No such user" }, 71184047Sobrien { EOPTNEG, "Option negotiation" }, 7121592Srgrimes { -1, 0 } 7131592Srgrimes}; 7141592Srgrimes 7151592Srgrimesstatic char * 7161592Srgrimeserrtomsg(error) 7171592Srgrimes int error; 7181592Srgrimes{ 7191592Srgrimes static char buf[20]; 7201592Srgrimes register struct errmsg *pe; 7211592Srgrimes if (error == 0) 7221592Srgrimes return "success"; 7231592Srgrimes for (pe = errmsgs; pe->e_code >= 0; pe++) 7241592Srgrimes if (pe->e_code == error) 7251592Srgrimes return pe->e_msg; 72624193Simp snprintf(buf, sizeof(buf), "error %d", error); 7271592Srgrimes return buf; 7281592Srgrimes} 7291592Srgrimes 7301592Srgrimes/* 7311592Srgrimes * Send a nak packet (error message). 7321592Srgrimes * Error code passed in is one of the 7331592Srgrimes * standard TFTP codes, or a UNIX errno 7341592Srgrimes * offset by 100. 7351592Srgrimes */ 7361592Srgrimesstatic void 7371592Srgrimesnak(error) 7381592Srgrimes int error; 7391592Srgrimes{ 7401592Srgrimes register struct tftphdr *tp; 7411592Srgrimes int length; 7421592Srgrimes register struct errmsg *pe; 7431592Srgrimes 7441592Srgrimes tp = (struct tftphdr *)buf; 7451592Srgrimes tp->th_opcode = htons((u_short)ERROR); 7461592Srgrimes tp->th_code = htons((u_short)error); 7471592Srgrimes for (pe = errmsgs; pe->e_code >= 0; pe++) 7481592Srgrimes if (pe->e_code == error) 7491592Srgrimes break; 7501592Srgrimes if (pe->e_code < 0) { 7511592Srgrimes pe->e_msg = strerror(error - 100); 7521592Srgrimes tp->th_code = EUNDEF; /* set 'undef' errorcode */ 7531592Srgrimes } 7541592Srgrimes strcpy(tp->th_msg, pe->e_msg); 7551592Srgrimes length = strlen(pe->e_msg); 7561592Srgrimes tp->th_msg[length] = '\0'; 7571592Srgrimes length += 5; 7581592Srgrimes if (send(peer, buf, length, 0) != length) 75931512Scharnier syslog(LOG_ERR, "nak: %m"); 7601592Srgrimes} 76184047Sobrien 76284047Sobrien/* 76384047Sobrien * Send an oack packet (option acknowledgement). 76484047Sobrien */ 76584047Sobrienstatic void 76684047Sobrienoack() 76784047Sobrien{ 76884047Sobrien struct tftphdr *tp, *ap; 76984047Sobrien int size, i, n; 77084047Sobrien char *bp; 77184047Sobrien 77284047Sobrien tp = (struct tftphdr *)buf; 77384047Sobrien bp = buf + 2; 77484047Sobrien size = sizeof(buf) - 2; 77584047Sobrien tp->th_opcode = htons((u_short)OACK); 77684047Sobrien for (i = 0; options[i].o_type != NULL; i++) { 77784047Sobrien if (options[i].o_request) { 77884047Sobrien n = snprintf(bp, size, "%s%c%d", options[i].o_type, 77984047Sobrien 0, options[i].o_reply); 78084047Sobrien bp += n+1; 78184047Sobrien size -= n+1; 78284047Sobrien if (size < 0) { 78384047Sobrien syslog(LOG_ERR, "oack: buffer overflow"); 78484047Sobrien exit(1); 78584047Sobrien } 78684047Sobrien } 78784047Sobrien } 78884047Sobrien size = bp - buf; 78984047Sobrien ap = (struct tftphdr *)ackbuf; 79084047Sobrien signal(SIGALRM, timer); 79184047Sobrien timeouts = 0; 79284047Sobrien 79384047Sobrien (void)setjmp(timeoutbuf); 79484047Sobrien if (send(peer, buf, size, 0) != size) { 79584047Sobrien syslog(LOG_INFO, "oack: %m"); 79684047Sobrien exit(1); 79784047Sobrien } 79884047Sobrien 79984047Sobrien for (;;) { 80084047Sobrien alarm(rexmtval); 80184047Sobrien n = recv(peer, ackbuf, sizeof (ackbuf), 0); 80284047Sobrien alarm(0); 80384047Sobrien if (n < 0) { 80484047Sobrien syslog(LOG_ERR, "recv: %m"); 80584047Sobrien exit(1); 80684047Sobrien } 80784047Sobrien ap->th_opcode = ntohs((u_short)ap->th_opcode); 80884047Sobrien ap->th_block = ntohs((u_short)ap->th_block); 80984047Sobrien if (ap->th_opcode == ERROR) 81084047Sobrien exit(1); 81184047Sobrien if (ap->th_opcode == ACK && ap->th_block == 0) 81284047Sobrien break; 81384047Sobrien } 81484047Sobrien} 815