tftpd.c revision 35152
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[] = 4535152Sphk "$Id: tftpd.c,v 1.10 1997/12/03 07:19:58 charnier Exp $"; 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> 681592Srgrimes#include <netdb.h> 6931512Scharnier#include <pwd.h> 701592Srgrimes#include <setjmp.h> 711592Srgrimes#include <signal.h> 721592Srgrimes#include <stdio.h> 731592Srgrimes#include <stdlib.h> 741592Srgrimes#include <string.h> 751592Srgrimes#include <syslog.h> 761592Srgrimes#include <unistd.h> 771592Srgrimes 781592Srgrimes#include "tftpsubs.h" 791592Srgrimes 801592Srgrimes#define TIMEOUT 5 811592Srgrimes 821592Srgrimesint peer; 831592Srgrimesint rexmtval = TIMEOUT; 841592Srgrimesint maxtimeout = 5*TIMEOUT; 851592Srgrimes 861592Srgrimes#define PKTSIZE SEGSIZE+4 871592Srgrimeschar buf[PKTSIZE]; 881592Srgrimeschar ackbuf[PKTSIZE]; 891592Srgrimesstruct sockaddr_in from; 901592Srgrimesint fromlen; 911592Srgrimes 921592Srgrimesvoid tftp __P((struct tftphdr *, int)); 931592Srgrimes 941592Srgrimes/* 951592Srgrimes * Null-terminated directory prefix list for absolute pathname requests and 961592Srgrimes * search list for relative pathname requests. 971592Srgrimes * 981592Srgrimes * MAXDIRS should be at least as large as the number of arguments that 991592Srgrimes * inetd allows (currently 20). 1001592Srgrimes */ 1011592Srgrimes#define MAXDIRS 20 1021592Srgrimesstatic struct dirlist { 1031592Srgrimes char *name; 1041592Srgrimes int len; 1051592Srgrimes} dirs[MAXDIRS+1]; 1061592Srgrimesstatic int suppress_naks; 1071592Srgrimesstatic int logging; 1081592Srgrimes 1091592Srgrimesstatic char *errtomsg __P((int)); 1101592Srgrimesstatic void nak __P((int)); 1111592Srgrimesstatic char *verifyhost __P((struct sockaddr_in *)); 1121592Srgrimes 1131592Srgrimesint 1141592Srgrimesmain(argc, argv) 1151592Srgrimes int argc; 1161592Srgrimes char *argv[]; 1171592Srgrimes{ 1181592Srgrimes register struct tftphdr *tp; 1191592Srgrimes register int n; 1201592Srgrimes int ch, on; 1211592Srgrimes struct sockaddr_in sin; 12218458Simp char *chroot_dir = NULL; 12318458Simp struct passwd *nobody; 1241592Srgrimes 12535152Sphk openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 12624349Simp while ((ch = getopt(argc, argv, "lns:")) != -1) { 1271592Srgrimes switch (ch) { 1281592Srgrimes case 'l': 1291592Srgrimes logging = 1; 1301592Srgrimes break; 1311592Srgrimes case 'n': 1321592Srgrimes suppress_naks = 1; 1331592Srgrimes break; 13418458Simp case 's': 13518458Simp chroot_dir = optarg; 13618458Simp break; 1371592Srgrimes default: 1381592Srgrimes syslog(LOG_WARNING, "ignoring unknown option -%c", ch); 1391592Srgrimes } 1401592Srgrimes } 1411592Srgrimes if (optind < argc) { 1421592Srgrimes struct dirlist *dirp; 1431592Srgrimes 1441592Srgrimes /* Get list of directory prefixes. Skip relative pathnames. */ 1451592Srgrimes for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; 1461592Srgrimes optind++) { 1471592Srgrimes if (argv[optind][0] == '/') { 1481592Srgrimes dirp->name = argv[optind]; 1491592Srgrimes dirp->len = strlen(dirp->name); 1501592Srgrimes dirp++; 1511592Srgrimes } 1521592Srgrimes } 1531592Srgrimes } 15418458Simp else if (chroot_dir) { 15518458Simp dirs->name = "/"; 15618458Simp dirs->len = 1; 15718458Simp } 1581592Srgrimes 1591592Srgrimes on = 1; 1601592Srgrimes if (ioctl(0, FIONBIO, &on) < 0) { 16131512Scharnier syslog(LOG_ERR, "ioctl(FIONBIO): %m"); 1621592Srgrimes exit(1); 1631592Srgrimes } 1641592Srgrimes fromlen = sizeof (from); 1651592Srgrimes n = recvfrom(0, buf, sizeof (buf), 0, 1661592Srgrimes (struct sockaddr *)&from, &fromlen); 1671592Srgrimes if (n < 0) { 16831512Scharnier syslog(LOG_ERR, "recvfrom: %m"); 1691592Srgrimes exit(1); 1701592Srgrimes } 1711592Srgrimes /* 1721592Srgrimes * Now that we have read the message out of the UDP 1731592Srgrimes * socket, we fork and exit. Thus, inetd will go back 1741592Srgrimes * to listening to the tftp port, and the next request 1751592Srgrimes * to come in will start up a new instance of tftpd. 1761592Srgrimes * 1771592Srgrimes * We do this so that inetd can run tftpd in "wait" mode. 1781592Srgrimes * The problem with tftpd running in "nowait" mode is that 1791592Srgrimes * inetd may get one or more successful "selects" on the 1801592Srgrimes * tftp port before we do our receive, so more than one 1811592Srgrimes * instance of tftpd may be started up. Worse, if tftpd 1821592Srgrimes * break before doing the above "recvfrom", inetd would 1831592Srgrimes * spawn endless instances, clogging the system. 1841592Srgrimes */ 1851592Srgrimes { 1861592Srgrimes int pid; 1871592Srgrimes int i, j; 1881592Srgrimes 1891592Srgrimes for (i = 1; i < 20; i++) { 1901592Srgrimes pid = fork(); 1911592Srgrimes if (pid < 0) { 1921592Srgrimes sleep(i); 1931592Srgrimes /* 1941592Srgrimes * flush out to most recently sent request. 1951592Srgrimes * 1961592Srgrimes * This may drop some request, but those 1971592Srgrimes * will be resent by the clients when 1981592Srgrimes * they timeout. The positive effect of 1991592Srgrimes * this flush is to (try to) prevent more 2001592Srgrimes * than one tftpd being started up to service 2011592Srgrimes * a single request from a single client. 2021592Srgrimes */ 2031592Srgrimes j = sizeof from; 2041592Srgrimes i = recvfrom(0, buf, sizeof (buf), 0, 2051592Srgrimes (struct sockaddr *)&from, &j); 2061592Srgrimes if (i > 0) { 2071592Srgrimes n = i; 2081592Srgrimes fromlen = j; 2091592Srgrimes } 2101592Srgrimes } else { 2111592Srgrimes break; 2121592Srgrimes } 2131592Srgrimes } 2141592Srgrimes if (pid < 0) { 21531512Scharnier syslog(LOG_ERR, "fork: %m"); 2161592Srgrimes exit(1); 2171592Srgrimes } else if (pid != 0) { 2181592Srgrimes exit(0); 2191592Srgrimes } 2201592Srgrimes } 22118458Simp 22218458Simp /* 22318458Simp * Since we exit here, we should do that only after the above 22418458Simp * recvfrom to keep inetd from constantly forking should there 22518458Simp * be a problem. See the above comment about system clogging. 22618458Simp */ 22718458Simp if (chroot_dir) { 22818458Simp /* Must get this before chroot because /etc might go away */ 22918458Simp if ((nobody = getpwnam("nobody")) == NULL) { 23018458Simp syslog(LOG_ERR, "nobody: no such user"); 23118458Simp exit(1); 23218458Simp } 23318458Simp if (chroot(chroot_dir)) { 23418458Simp syslog(LOG_ERR, "chroot: %s: %m", chroot_dir); 23518458Simp exit(1); 23618458Simp } 23718458Simp chdir( "/" ); 23818458Simp setuid(nobody->pw_uid); 23918458Simp } 24018458Simp 2411592Srgrimes from.sin_family = AF_INET; 2421592Srgrimes alarm(0); 2431592Srgrimes close(0); 2441592Srgrimes close(1); 2451592Srgrimes peer = socket(AF_INET, SOCK_DGRAM, 0); 2461592Srgrimes if (peer < 0) { 24731512Scharnier syslog(LOG_ERR, "socket: %m"); 2481592Srgrimes exit(1); 2491592Srgrimes } 2501592Srgrimes memset(&sin, 0, sizeof(sin)); 2511592Srgrimes sin.sin_family = AF_INET; 2521592Srgrimes if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) { 25331512Scharnier syslog(LOG_ERR, "bind: %m"); 2541592Srgrimes exit(1); 2551592Srgrimes } 2561592Srgrimes if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) { 25731512Scharnier syslog(LOG_ERR, "connect: %m"); 2581592Srgrimes exit(1); 2591592Srgrimes } 2601592Srgrimes tp = (struct tftphdr *)buf; 2611592Srgrimes tp->th_opcode = ntohs(tp->th_opcode); 2621592Srgrimes if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 2631592Srgrimes tftp(tp, n); 2641592Srgrimes exit(1); 2651592Srgrimes} 2661592Srgrimes 2671592Srgrimesstruct formats; 2681592Srgrimesint validate_access __P((char **, int)); 2691592Srgrimesvoid sendfile __P((struct formats *)); 2701592Srgrimesvoid recvfile __P((struct formats *)); 2711592Srgrimes 2721592Srgrimesstruct formats { 2731592Srgrimes char *f_mode; 2741592Srgrimes int (*f_validate) __P((char **, int)); 2751592Srgrimes void (*f_send) __P((struct formats *)); 2761592Srgrimes void (*f_recv) __P((struct formats *)); 2771592Srgrimes int f_convert; 2781592Srgrimes} formats[] = { 2791592Srgrimes { "netascii", validate_access, sendfile, recvfile, 1 }, 2801592Srgrimes { "octet", validate_access, sendfile, recvfile, 0 }, 2811592Srgrimes#ifdef notdef 2821592Srgrimes { "mail", validate_user, sendmail, recvmail, 1 }, 2831592Srgrimes#endif 2841592Srgrimes { 0 } 2851592Srgrimes}; 2861592Srgrimes 2871592Srgrimes/* 2881592Srgrimes * Handle initial connection protocol. 2891592Srgrimes */ 2901592Srgrimesvoid 2911592Srgrimestftp(tp, size) 2921592Srgrimes struct tftphdr *tp; 2931592Srgrimes int size; 2941592Srgrimes{ 2951592Srgrimes register char *cp; 2961592Srgrimes int first = 1, ecode; 2971592Srgrimes register struct formats *pf; 2981592Srgrimes char *filename, *mode; 2991592Srgrimes 3001592Srgrimes filename = cp = tp->th_stuff; 3011592Srgrimesagain: 3021592Srgrimes while (cp < buf + size) { 3031592Srgrimes if (*cp == '\0') 3041592Srgrimes break; 3051592Srgrimes cp++; 3061592Srgrimes } 3071592Srgrimes if (*cp != '\0') { 3081592Srgrimes nak(EBADOP); 3091592Srgrimes exit(1); 3101592Srgrimes } 3111592Srgrimes if (first) { 3121592Srgrimes mode = ++cp; 3131592Srgrimes first = 0; 3141592Srgrimes goto again; 3151592Srgrimes } 3161592Srgrimes for (cp = mode; *cp; cp++) 3171592Srgrimes if (isupper(*cp)) 3181592Srgrimes *cp = tolower(*cp); 3191592Srgrimes for (pf = formats; pf->f_mode; pf++) 3201592Srgrimes if (strcmp(pf->f_mode, mode) == 0) 3211592Srgrimes break; 3221592Srgrimes if (pf->f_mode == 0) { 3231592Srgrimes nak(EBADOP); 3241592Srgrimes exit(1); 3251592Srgrimes } 3261592Srgrimes ecode = (*pf->f_validate)(&filename, tp->th_opcode); 3271592Srgrimes if (logging) { 3281592Srgrimes syslog(LOG_INFO, "%s: %s request for %s: %s", 3291592Srgrimes verifyhost(&from), 3301592Srgrimes tp->th_opcode == WRQ ? "write" : "read", 3311592Srgrimes filename, errtomsg(ecode)); 3321592Srgrimes } 3331592Srgrimes if (ecode) { 3341592Srgrimes /* 3351592Srgrimes * Avoid storms of naks to a RRQ broadcast for a relative 3361592Srgrimes * bootfile pathname from a diskless Sun. 3371592Srgrimes */ 3381592Srgrimes if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 3391592Srgrimes exit(0); 3401592Srgrimes nak(ecode); 3411592Srgrimes exit(1); 3421592Srgrimes } 3431592Srgrimes if (tp->th_opcode == WRQ) 3441592Srgrimes (*pf->f_recv)(pf); 3451592Srgrimes else 3461592Srgrimes (*pf->f_send)(pf); 3471592Srgrimes exit(0); 3481592Srgrimes} 3491592Srgrimes 3501592Srgrimes 3511592SrgrimesFILE *file; 3521592Srgrimes 3531592Srgrimes/* 3541592Srgrimes * Validate file access. Since we 3551592Srgrimes * have no uid or gid, for now require 3561592Srgrimes * file to exist and be publicly 3571592Srgrimes * readable/writable. 3581592Srgrimes * If we were invoked with arguments 3591592Srgrimes * from inetd then the file must also be 3601592Srgrimes * in one of the given directory prefixes. 3611592Srgrimes * Note also, full path name must be 3621592Srgrimes * given as we have no login directory. 3631592Srgrimes */ 3641592Srgrimesint 3651592Srgrimesvalidate_access(filep, mode) 3661592Srgrimes char **filep; 3671592Srgrimes int mode; 3681592Srgrimes{ 3691592Srgrimes struct stat stbuf; 3701592Srgrimes int fd; 3711592Srgrimes struct dirlist *dirp; 3721592Srgrimes static char pathname[MAXPATHLEN]; 3731592Srgrimes char *filename = *filep; 3741592Srgrimes 3751592Srgrimes /* 3761592Srgrimes * Prevent tricksters from getting around the directory restrictions 3771592Srgrimes */ 3781592Srgrimes if (strstr(filename, "/../")) 3791592Srgrimes return (EACCESS); 3801592Srgrimes 3811592Srgrimes if (*filename == '/') { 3821592Srgrimes /* 3831592Srgrimes * Allow the request if it's in one of the approved locations. 3841592Srgrimes * Special case: check the null prefix ("/") by looking 3851592Srgrimes * for length = 1 and relying on the arg. processing that 3861592Srgrimes * it's a /. 3871592Srgrimes */ 3881592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 3891592Srgrimes if (dirp->len == 1 || 3901592Srgrimes (!strncmp(filename, dirp->name, dirp->len) && 3911592Srgrimes filename[dirp->len] == '/')) 3921592Srgrimes break; 3931592Srgrimes } 3941592Srgrimes /* If directory list is empty, allow access to any file */ 3951592Srgrimes if (dirp->name == NULL && dirp != dirs) 3961592Srgrimes return (EACCESS); 3971592Srgrimes if (stat(filename, &stbuf) < 0) 3981592Srgrimes return (errno == ENOENT ? ENOTFOUND : EACCESS); 3991592Srgrimes if ((stbuf.st_mode & S_IFMT) != S_IFREG) 4001592Srgrimes return (ENOTFOUND); 4011592Srgrimes if (mode == RRQ) { 4021592Srgrimes if ((stbuf.st_mode & S_IROTH) == 0) 4031592Srgrimes return (EACCESS); 4041592Srgrimes } else { 4051592Srgrimes if ((stbuf.st_mode & S_IWOTH) == 0) 4061592Srgrimes return (EACCESS); 4071592Srgrimes } 4081592Srgrimes } else { 4091592Srgrimes int err; 4101592Srgrimes 4111592Srgrimes /* 4121592Srgrimes * Relative file name: search the approved locations for it. 4136750Sjkh * Don't allow write requests that avoid directory 4141592Srgrimes * restrictions. 4151592Srgrimes */ 4161592Srgrimes 4176750Sjkh if (!strncmp(filename, "../", 3)) 4181592Srgrimes return (EACCESS); 4191592Srgrimes 4201592Srgrimes /* 4211592Srgrimes * If the file exists in one of the directories and isn't 4221592Srgrimes * readable, continue looking. However, change the error code 4231592Srgrimes * to give an indication that the file exists. 4241592Srgrimes */ 4251592Srgrimes err = ENOTFOUND; 4261592Srgrimes for (dirp = dirs; dirp->name != NULL; dirp++) { 42724193Simp snprintf(pathname, sizeof(pathname), "%s/%s", 42824193Simp dirp->name, filename); 4291592Srgrimes if (stat(pathname, &stbuf) == 0 && 4301592Srgrimes (stbuf.st_mode & S_IFMT) == S_IFREG) { 4311592Srgrimes if ((stbuf.st_mode & S_IROTH) != 0) { 4321592Srgrimes break; 4331592Srgrimes } 4341592Srgrimes err = EACCESS; 4351592Srgrimes } 4361592Srgrimes } 4371592Srgrimes if (dirp->name == NULL) 4381592Srgrimes return (err); 4391592Srgrimes *filep = filename = pathname; 4401592Srgrimes } 44120052Sjoerg fd = open(filename, mode == RRQ ? O_RDONLY : O_WRONLY|O_TRUNC); 4421592Srgrimes if (fd < 0) 4431592Srgrimes return (errno + 100); 4441592Srgrimes file = fdopen(fd, (mode == RRQ)? "r":"w"); 4451592Srgrimes if (file == NULL) { 4461592Srgrimes return errno+100; 4471592Srgrimes } 4481592Srgrimes return (0); 4491592Srgrimes} 4501592Srgrimes 4511592Srgrimesint timeout; 4521592Srgrimesjmp_buf timeoutbuf; 4531592Srgrimes 4541592Srgrimesvoid 4551592Srgrimestimer() 4561592Srgrimes{ 4571592Srgrimes 4581592Srgrimes timeout += rexmtval; 4591592Srgrimes if (timeout >= maxtimeout) 4601592Srgrimes exit(1); 4611592Srgrimes longjmp(timeoutbuf, 1); 4621592Srgrimes} 4631592Srgrimes 4641592Srgrimes/* 4651592Srgrimes * Send the requested file. 4661592Srgrimes */ 4671592Srgrimesvoid 4681592Srgrimessendfile(pf) 4691592Srgrimes struct formats *pf; 4701592Srgrimes{ 4711592Srgrimes struct tftphdr *dp, *r_init(); 4721592Srgrimes register struct tftphdr *ap; /* ack packet */ 4731592Srgrimes register int size, n; 4741592Srgrimes volatile int block; 4751592Srgrimes 4761592Srgrimes signal(SIGALRM, timer); 4771592Srgrimes dp = r_init(); 4781592Srgrimes ap = (struct tftphdr *)ackbuf; 4791592Srgrimes block = 1; 4801592Srgrimes do { 4811592Srgrimes size = readit(file, &dp, pf->f_convert); 4821592Srgrimes if (size < 0) { 4831592Srgrimes nak(errno + 100); 4841592Srgrimes goto abort; 4851592Srgrimes } 4861592Srgrimes dp->th_opcode = htons((u_short)DATA); 4871592Srgrimes dp->th_block = htons((u_short)block); 4881592Srgrimes timeout = 0; 4891592Srgrimes (void)setjmp(timeoutbuf); 4901592Srgrimes 4911592Srgrimessend_data: 4921592Srgrimes if (send(peer, dp, size + 4, 0) != size + 4) { 49331512Scharnier syslog(LOG_ERR, "write: %m"); 4941592Srgrimes goto abort; 4951592Srgrimes } 4961592Srgrimes read_ahead(file, pf->f_convert); 4971592Srgrimes for ( ; ; ) { 4981592Srgrimes alarm(rexmtval); /* read the ack */ 4991592Srgrimes n = recv(peer, ackbuf, sizeof (ackbuf), 0); 5001592Srgrimes alarm(0); 5011592Srgrimes if (n < 0) { 50231512Scharnier syslog(LOG_ERR, "read: %m"); 5031592Srgrimes goto abort; 5041592Srgrimes } 5051592Srgrimes ap->th_opcode = ntohs((u_short)ap->th_opcode); 5061592Srgrimes ap->th_block = ntohs((u_short)ap->th_block); 5071592Srgrimes 5081592Srgrimes if (ap->th_opcode == ERROR) 5091592Srgrimes goto abort; 5101592Srgrimes 5111592Srgrimes if (ap->th_opcode == ACK) { 5121592Srgrimes if (ap->th_block == block) 5131592Srgrimes break; 5141592Srgrimes /* Re-synchronize with the other side */ 5151592Srgrimes (void) synchnet(peer); 5161592Srgrimes if (ap->th_block == (block -1)) 5171592Srgrimes goto send_data; 5181592Srgrimes } 5191592Srgrimes 5201592Srgrimes } 5211592Srgrimes block++; 5221592Srgrimes } while (size == SEGSIZE); 5231592Srgrimesabort: 5241592Srgrimes (void) fclose(file); 5251592Srgrimes} 5261592Srgrimes 5271592Srgrimesvoid 5281592Srgrimesjustquit() 5291592Srgrimes{ 5301592Srgrimes exit(0); 5311592Srgrimes} 5321592Srgrimes 5331592Srgrimes 5341592Srgrimes/* 5351592Srgrimes * Receive a file. 5361592Srgrimes */ 5371592Srgrimesvoid 5381592Srgrimesrecvfile(pf) 5391592Srgrimes struct formats *pf; 5401592Srgrimes{ 5411592Srgrimes struct tftphdr *dp, *w_init(); 5421592Srgrimes register struct tftphdr *ap; /* ack buffer */ 5431592Srgrimes register int n, size; 5441592Srgrimes volatile int block; 5451592Srgrimes 5461592Srgrimes signal(SIGALRM, timer); 5471592Srgrimes dp = w_init(); 5481592Srgrimes ap = (struct tftphdr *)ackbuf; 5491592Srgrimes block = 0; 5501592Srgrimes do { 5511592Srgrimes timeout = 0; 5521592Srgrimes ap->th_opcode = htons((u_short)ACK); 5531592Srgrimes ap->th_block = htons((u_short)block); 5541592Srgrimes block++; 5551592Srgrimes (void) setjmp(timeoutbuf); 5561592Srgrimessend_ack: 5571592Srgrimes if (send(peer, ackbuf, 4, 0) != 4) { 55831512Scharnier syslog(LOG_ERR, "write: %m"); 5591592Srgrimes goto abort; 5601592Srgrimes } 5611592Srgrimes write_behind(file, pf->f_convert); 5621592Srgrimes for ( ; ; ) { 5631592Srgrimes alarm(rexmtval); 5641592Srgrimes n = recv(peer, dp, PKTSIZE, 0); 5651592Srgrimes alarm(0); 5661592Srgrimes if (n < 0) { /* really? */ 56731512Scharnier syslog(LOG_ERR, "read: %m"); 5681592Srgrimes goto abort; 5691592Srgrimes } 5701592Srgrimes dp->th_opcode = ntohs((u_short)dp->th_opcode); 5711592Srgrimes dp->th_block = ntohs((u_short)dp->th_block); 5721592Srgrimes if (dp->th_opcode == ERROR) 5731592Srgrimes goto abort; 5741592Srgrimes if (dp->th_opcode == DATA) { 5751592Srgrimes if (dp->th_block == block) { 5761592Srgrimes break; /* normal */ 5771592Srgrimes } 5781592Srgrimes /* Re-synchronize with the other side */ 5791592Srgrimes (void) synchnet(peer); 5801592Srgrimes if (dp->th_block == (block-1)) 5811592Srgrimes goto send_ack; /* rexmit */ 5821592Srgrimes } 5831592Srgrimes } 5841592Srgrimes /* size = write(file, dp->th_data, n - 4); */ 5851592Srgrimes size = writeit(file, &dp, n - 4, pf->f_convert); 5861592Srgrimes if (size != (n-4)) { /* ahem */ 5871592Srgrimes if (size < 0) nak(errno + 100); 5881592Srgrimes else nak(ENOSPACE); 5891592Srgrimes goto abort; 5901592Srgrimes } 5911592Srgrimes } while (size == SEGSIZE); 5921592Srgrimes write_behind(file, pf->f_convert); 5931592Srgrimes (void) fclose(file); /* close data file */ 5941592Srgrimes 5951592Srgrimes ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 5961592Srgrimes ap->th_block = htons((u_short)(block)); 5971592Srgrimes (void) send(peer, ackbuf, 4, 0); 5981592Srgrimes 5991592Srgrimes signal(SIGALRM, justquit); /* just quit on timeout */ 6001592Srgrimes alarm(rexmtval); 6011592Srgrimes n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 6021592Srgrimes alarm(0); 6031592Srgrimes if (n >= 4 && /* if read some data */ 6041592Srgrimes dp->th_opcode == DATA && /* and got a data block */ 6051592Srgrimes block == dp->th_block) { /* then my last ack was lost */ 6061592Srgrimes (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 6071592Srgrimes } 6081592Srgrimesabort: 6091592Srgrimes return; 6101592Srgrimes} 6111592Srgrimes 6121592Srgrimesstruct errmsg { 6131592Srgrimes int e_code; 6141592Srgrimes char *e_msg; 6151592Srgrimes} errmsgs[] = { 6161592Srgrimes { EUNDEF, "Undefined error code" }, 6171592Srgrimes { ENOTFOUND, "File not found" }, 6181592Srgrimes { EACCESS, "Access violation" }, 6191592Srgrimes { ENOSPACE, "Disk full or allocation exceeded" }, 6201592Srgrimes { EBADOP, "Illegal TFTP operation" }, 6211592Srgrimes { EBADID, "Unknown transfer ID" }, 6221592Srgrimes { EEXISTS, "File already exists" }, 6231592Srgrimes { ENOUSER, "No such user" }, 6241592Srgrimes { -1, 0 } 6251592Srgrimes}; 6261592Srgrimes 6271592Srgrimesstatic char * 6281592Srgrimeserrtomsg(error) 6291592Srgrimes int error; 6301592Srgrimes{ 6311592Srgrimes static char buf[20]; 6321592Srgrimes register struct errmsg *pe; 6331592Srgrimes if (error == 0) 6341592Srgrimes return "success"; 6351592Srgrimes for (pe = errmsgs; pe->e_code >= 0; pe++) 6361592Srgrimes if (pe->e_code == error) 6371592Srgrimes return pe->e_msg; 63824193Simp snprintf(buf, sizeof(buf), "error %d", error); 6391592Srgrimes return buf; 6401592Srgrimes} 6411592Srgrimes 6421592Srgrimes/* 6431592Srgrimes * Send a nak packet (error message). 6441592Srgrimes * Error code passed in is one of the 6451592Srgrimes * standard TFTP codes, or a UNIX errno 6461592Srgrimes * offset by 100. 6471592Srgrimes */ 6481592Srgrimesstatic void 6491592Srgrimesnak(error) 6501592Srgrimes int error; 6511592Srgrimes{ 6521592Srgrimes register struct tftphdr *tp; 6531592Srgrimes int length; 6541592Srgrimes register struct errmsg *pe; 6551592Srgrimes 6561592Srgrimes tp = (struct tftphdr *)buf; 6571592Srgrimes tp->th_opcode = htons((u_short)ERROR); 6581592Srgrimes tp->th_code = htons((u_short)error); 6591592Srgrimes for (pe = errmsgs; pe->e_code >= 0; pe++) 6601592Srgrimes if (pe->e_code == error) 6611592Srgrimes break; 6621592Srgrimes if (pe->e_code < 0) { 6631592Srgrimes pe->e_msg = strerror(error - 100); 6641592Srgrimes tp->th_code = EUNDEF; /* set 'undef' errorcode */ 6651592Srgrimes } 6661592Srgrimes strcpy(tp->th_msg, pe->e_msg); 6671592Srgrimes length = strlen(pe->e_msg); 6681592Srgrimes tp->th_msg[length] = '\0'; 6691592Srgrimes length += 5; 6701592Srgrimes if (send(peer, buf, length, 0) != length) 67131512Scharnier syslog(LOG_ERR, "nak: %m"); 6721592Srgrimes} 6731592Srgrimes 6741592Srgrimesstatic char * 6751592Srgrimesverifyhost(fromp) 6761592Srgrimes struct sockaddr_in *fromp; 6771592Srgrimes{ 6781592Srgrimes struct hostent *hp; 6791592Srgrimes 6801592Srgrimes hp = gethostbyaddr((char *)&fromp->sin_addr, sizeof (fromp->sin_addr), 6811592Srgrimes fromp->sin_family); 6821592Srgrimes if (hp) 6831592Srgrimes return hp->h_name; 6841592Srgrimes else 6851592Srgrimes return inet_ntoa(fromp->sin_addr); 6861592Srgrimes} 687