1ea022d16SRodney W. Grimes /* 2ea022d16SRodney W. Grimes * Copyright (c) 1983, 1993 3ea022d16SRodney W. Grimes * The Regents of the University of California. All rights reserved. 4ea022d16SRodney W. Grimes * 5ea022d16SRodney W. Grimes * Redistribution and use in source and binary forms, with or without 6ea022d16SRodney W. Grimes * modification, are permitted provided that the following conditions 7ea022d16SRodney W. Grimes * are met: 8ea022d16SRodney W. Grimes * 1. Redistributions of source code must retain the above copyright 9ea022d16SRodney W. Grimes * notice, this list of conditions and the following disclaimer. 10ea022d16SRodney W. Grimes * 2. Redistributions in binary form must reproduce the above copyright 11ea022d16SRodney W. Grimes * notice, this list of conditions and the following disclaimer in the 12ea022d16SRodney W. Grimes * documentation and/or other materials provided with the distribution. 13ea022d16SRodney W. Grimes * 3. All advertising materials mentioning features or use of this software 14ea022d16SRodney W. Grimes * must display the following acknowledgement: 15ea022d16SRodney W. Grimes * This product includes software developed by the University of 16ea022d16SRodney W. Grimes * California, Berkeley and its contributors. 17ea022d16SRodney W. Grimes * 4. Neither the name of the University nor the names of its contributors 18ea022d16SRodney W. Grimes * may be used to endorse or promote products derived from this software 19ea022d16SRodney W. Grimes * without specific prior written permission. 20ea022d16SRodney W. Grimes * 21ea022d16SRodney W. Grimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22ea022d16SRodney W. Grimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23ea022d16SRodney W. Grimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24ea022d16SRodney W. Grimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25ea022d16SRodney W. Grimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26ea022d16SRodney W. Grimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27ea022d16SRodney W. Grimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28ea022d16SRodney W. Grimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29ea022d16SRodney W. Grimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30ea022d16SRodney W. Grimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31ea022d16SRodney W. Grimes * SUCH DAMAGE. 32ea022d16SRodney W. Grimes */ 33ea022d16SRodney W. Grimes 34ea022d16SRodney W. Grimes #ifndef lint 35a8faeabcSPhilippe Charnier static const char copyright[] = 36ea022d16SRodney W. Grimes "@(#) Copyright (c) 1983, 1993\n\ 37ea022d16SRodney W. Grimes The Regents of the University of California. All rights reserved.\n"; 38ea022d16SRodney W. Grimes #endif /* not lint */ 39ea022d16SRodney W. Grimes 40ea022d16SRodney W. Grimes #ifndef lint 41a8faeabcSPhilippe Charnier #if 0 42ea022d16SRodney W. Grimes static char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93"; 43a8faeabcSPhilippe Charnier #endif 44a8faeabcSPhilippe Charnier static const char rcsid[] = 457f3dea24SPeter Wemm "$FreeBSD$"; 46ea022d16SRodney W. Grimes #endif /* not lint */ 47ea022d16SRodney W. Grimes 48ea022d16SRodney W. Grimes /* 49ea022d16SRodney W. Grimes * Trivial file transfer protocol server. 50ea022d16SRodney W. Grimes * 51ea022d16SRodney W. Grimes * This version includes many modifications by Jim Guyton 52ea022d16SRodney W. Grimes * <guyton@rand-unix>. 53ea022d16SRodney W. Grimes */ 54ea022d16SRodney W. Grimes 55ea022d16SRodney W. Grimes #include <sys/param.h> 56ea022d16SRodney W. Grimes #include <sys/ioctl.h> 57ea022d16SRodney W. Grimes #include <sys/stat.h> 58ea022d16SRodney W. Grimes #include <sys/socket.h> 598ea31785SWarner Losh #include <sys/types.h> 603ec73cf1SBrian Somers #include <sys/time.h> 61ea022d16SRodney W. Grimes 62ea022d16SRodney W. Grimes #include <netinet/in.h> 63ea022d16SRodney W. Grimes #include <arpa/tftp.h> 64ea022d16SRodney W. Grimes #include <arpa/inet.h> 65ea022d16SRodney W. Grimes 66ea022d16SRodney W. Grimes #include <ctype.h> 67ea022d16SRodney W. Grimes #include <errno.h> 68ea022d16SRodney W. Grimes #include <fcntl.h> 6932af26a5SBrian Somers #include <libutil.h> 70ea022d16SRodney W. Grimes #include <netdb.h> 71a8faeabcSPhilippe Charnier #include <pwd.h> 72ea022d16SRodney W. Grimes #include <setjmp.h> 73ea022d16SRodney W. Grimes #include <signal.h> 74ea022d16SRodney W. Grimes #include <stdio.h> 75ea022d16SRodney W. Grimes #include <stdlib.h> 76ea022d16SRodney W. Grimes #include <string.h> 77ea022d16SRodney W. Grimes #include <syslog.h> 78ea022d16SRodney W. Grimes #include <unistd.h> 79ea022d16SRodney W. Grimes 80ea022d16SRodney W. Grimes #include "tftpsubs.h" 81ea022d16SRodney W. Grimes 82ea022d16SRodney W. Grimes #define TIMEOUT 5 83c9374115SDavid E. O'Brien #define MAX_TIMEOUTS 5 84ea022d16SRodney W. Grimes 85ea022d16SRodney W. Grimes int peer; 86ea022d16SRodney W. Grimes int rexmtval = TIMEOUT; 87c9374115SDavid E. O'Brien int max_rexmtval = 2*TIMEOUT; 88ea022d16SRodney W. Grimes 89ea022d16SRodney W. Grimes #define PKTSIZE SEGSIZE+4 90ea022d16SRodney W. Grimes char buf[PKTSIZE]; 91ea022d16SRodney W. Grimes char ackbuf[PKTSIZE]; 924dac6235SHajimu UMEMOTO struct sockaddr_storage from; 93ea022d16SRodney W. Grimes 94dc4c3024SWarner Losh void tftp(struct tftphdr *, int); 954dac6235SHajimu UMEMOTO static void unmappedaddr(struct sockaddr_in6 *); 96ea022d16SRodney W. Grimes 97ea022d16SRodney W. Grimes /* 98ea022d16SRodney W. Grimes * Null-terminated directory prefix list for absolute pathname requests and 99ea022d16SRodney W. Grimes * search list for relative pathname requests. 100ea022d16SRodney W. Grimes * 101ea022d16SRodney W. Grimes * MAXDIRS should be at least as large as the number of arguments that 102ea022d16SRodney W. Grimes * inetd allows (currently 20). 103ea022d16SRodney W. Grimes */ 104ea022d16SRodney W. Grimes #define MAXDIRS 20 105ea022d16SRodney W. Grimes static struct dirlist { 106f49c0dc0SDavid Malone const char *name; 107ea022d16SRodney W. Grimes int len; 108ea022d16SRodney W. Grimes } dirs[MAXDIRS+1]; 109ea022d16SRodney W. Grimes static int suppress_naks; 110ea022d16SRodney W. Grimes static int logging; 1111ed0e5d2SBill Fumerola static int ipchroot; 112eff77877SMatthew N. Dodd static int create_new = 0; 113eff77877SMatthew N. Dodd static mode_t mask = S_IWGRP|S_IWOTH; 114ea022d16SRodney W. Grimes 115f49c0dc0SDavid Malone static const char *errtomsg(int); 116dc4c3024SWarner Losh static void nak(int); 117f49c0dc0SDavid Malone static void oack(void); 118f49c0dc0SDavid Malone 119f49c0dc0SDavid Malone static void timer(int); 120f49c0dc0SDavid Malone static void justquit(int); 121ea022d16SRodney W. Grimes 122ea022d16SRodney W. Grimes int 123dc4c3024SWarner Losh main(int argc, char *argv[]) 124ea022d16SRodney W. Grimes { 125dc4c3024SWarner Losh struct tftphdr *tp; 1264359d8a3SStefan Farfeleder socklen_t fromlen, len; 127dc4c3024SWarner Losh int n; 128ea022d16SRodney W. Grimes int ch, on; 1294dac6235SHajimu UMEMOTO struct sockaddr_storage me; 1308ea31785SWarner Losh char *chroot_dir = NULL; 1318ea31785SWarner Losh struct passwd *nobody; 132f49c0dc0SDavid Malone const char *chuser = "nobody"; 133ea022d16SRodney W. Grimes 1343ec73cf1SBrian Somers tzset(); /* syslog in localtime */ 1353ec73cf1SBrian Somers 13620ef8838SPoul-Henning Kamp openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 137eff77877SMatthew N. Dodd while ((ch = getopt(argc, argv, "cClns:u:Uw")) != -1) { 138ea022d16SRodney W. Grimes switch (ch) { 1391ed0e5d2SBill Fumerola case 'c': 1401ed0e5d2SBill Fumerola ipchroot = 1; 1411ed0e5d2SBill Fumerola break; 1421ed0e5d2SBill Fumerola case 'C': 1431ed0e5d2SBill Fumerola ipchroot = 2; 1441ed0e5d2SBill Fumerola break; 145ea022d16SRodney W. Grimes case 'l': 146ea022d16SRodney W. Grimes logging = 1; 147ea022d16SRodney W. Grimes break; 148ea022d16SRodney W. Grimes case 'n': 149ea022d16SRodney W. Grimes suppress_naks = 1; 150ea022d16SRodney W. Grimes break; 1518ea31785SWarner Losh case 's': 1528ea31785SWarner Losh chroot_dir = optarg; 1538ea31785SWarner Losh break; 154f62eaadfSGarrett Wollman case 'u': 155f62eaadfSGarrett Wollman chuser = optarg; 156f62eaadfSGarrett Wollman break; 157eff77877SMatthew N. Dodd case 'U': 158eff77877SMatthew N. Dodd mask = strtol(optarg, NULL, 0); 159eff77877SMatthew N. Dodd break; 160eff77877SMatthew N. Dodd case 'w': 161eff77877SMatthew N. Dodd create_new = 1; 162eff77877SMatthew N. Dodd break; 163ea022d16SRodney W. Grimes default: 164ea022d16SRodney W. Grimes syslog(LOG_WARNING, "ignoring unknown option -%c", ch); 165ea022d16SRodney W. Grimes } 166ea022d16SRodney W. Grimes } 167ea022d16SRodney W. Grimes if (optind < argc) { 168ea022d16SRodney W. Grimes struct dirlist *dirp; 169ea022d16SRodney W. Grimes 170ea022d16SRodney W. Grimes /* Get list of directory prefixes. Skip relative pathnames. */ 171ea022d16SRodney W. Grimes for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; 172ea022d16SRodney W. Grimes optind++) { 173ea022d16SRodney W. Grimes if (argv[optind][0] == '/') { 174ea022d16SRodney W. Grimes dirp->name = argv[optind]; 175ea022d16SRodney W. Grimes dirp->len = strlen(dirp->name); 176ea022d16SRodney W. Grimes dirp++; 177ea022d16SRodney W. Grimes } 178ea022d16SRodney W. Grimes } 179ea022d16SRodney W. Grimes } 1808ea31785SWarner Losh else if (chroot_dir) { 1818ea31785SWarner Losh dirs->name = "/"; 1828ea31785SWarner Losh dirs->len = 1; 1838ea31785SWarner Losh } 184a273f3aeSBill Fumerola if (ipchroot > 0 && chroot_dir == NULL) { 1851ed0e5d2SBill Fumerola syslog(LOG_ERR, "-c requires -s"); 1861ed0e5d2SBill Fumerola exit(1); 1871ed0e5d2SBill Fumerola } 188ea022d16SRodney W. Grimes 189eff77877SMatthew N. Dodd umask(mask); 190eff77877SMatthew N. Dodd 191ea022d16SRodney W. Grimes on = 1; 192ea022d16SRodney W. Grimes if (ioctl(0, FIONBIO, &on) < 0) { 193a8faeabcSPhilippe Charnier syslog(LOG_ERR, "ioctl(FIONBIO): %m"); 194ea022d16SRodney W. Grimes exit(1); 195ea022d16SRodney W. Grimes } 196ea022d16SRodney W. Grimes fromlen = sizeof (from); 197ea022d16SRodney W. Grimes n = recvfrom(0, buf, sizeof (buf), 0, 198ea022d16SRodney W. Grimes (struct sockaddr *)&from, &fromlen); 199ea022d16SRodney W. Grimes if (n < 0) { 200a8faeabcSPhilippe Charnier syslog(LOG_ERR, "recvfrom: %m"); 201ea022d16SRodney W. Grimes exit(1); 202ea022d16SRodney W. Grimes } 203ea022d16SRodney W. Grimes /* 204ea022d16SRodney W. Grimes * Now that we have read the message out of the UDP 205ea022d16SRodney W. Grimes * socket, we fork and exit. Thus, inetd will go back 206ea022d16SRodney W. Grimes * to listening to the tftp port, and the next request 207ea022d16SRodney W. Grimes * to come in will start up a new instance of tftpd. 208ea022d16SRodney W. Grimes * 209ea022d16SRodney W. Grimes * We do this so that inetd can run tftpd in "wait" mode. 210ea022d16SRodney W. Grimes * The problem with tftpd running in "nowait" mode is that 211ea022d16SRodney W. Grimes * inetd may get one or more successful "selects" on the 212ea022d16SRodney W. Grimes * tftp port before we do our receive, so more than one 213ea022d16SRodney W. Grimes * instance of tftpd may be started up. Worse, if tftpd 214ea022d16SRodney W. Grimes * break before doing the above "recvfrom", inetd would 215ea022d16SRodney W. Grimes * spawn endless instances, clogging the system. 216ea022d16SRodney W. Grimes */ 217ea022d16SRodney W. Grimes { 2184359d8a3SStefan Farfeleder int i, pid; 219ea022d16SRodney W. Grimes 220ea022d16SRodney W. Grimes for (i = 1; i < 20; i++) { 221ea022d16SRodney W. Grimes pid = fork(); 222ea022d16SRodney W. Grimes if (pid < 0) { 223ea022d16SRodney W. Grimes sleep(i); 224ea022d16SRodney W. Grimes /* 225ea022d16SRodney W. Grimes * flush out to most recently sent request. 226ea022d16SRodney W. Grimes * 227ea022d16SRodney W. Grimes * This may drop some request, but those 228ea022d16SRodney W. Grimes * will be resent by the clients when 229ea022d16SRodney W. Grimes * they timeout. The positive effect of 230ea022d16SRodney W. Grimes * this flush is to (try to) prevent more 231ea022d16SRodney W. Grimes * than one tftpd being started up to service 232ea022d16SRodney W. Grimes * a single request from a single client. 233ea022d16SRodney W. Grimes */ 2344359d8a3SStefan Farfeleder fromlen = sizeof from; 235ea022d16SRodney W. Grimes i = recvfrom(0, buf, sizeof (buf), 0, 2364359d8a3SStefan Farfeleder (struct sockaddr *)&from, &fromlen); 237ea022d16SRodney W. Grimes if (i > 0) { 238ea022d16SRodney W. Grimes n = i; 239ea022d16SRodney W. Grimes } 240ea022d16SRodney W. Grimes } else { 241ea022d16SRodney W. Grimes break; 242ea022d16SRodney W. Grimes } 243ea022d16SRodney W. Grimes } 244ea022d16SRodney W. Grimes if (pid < 0) { 245a8faeabcSPhilippe Charnier syslog(LOG_ERR, "fork: %m"); 246ea022d16SRodney W. Grimes exit(1); 247ea022d16SRodney W. Grimes } else if (pid != 0) { 248ea022d16SRodney W. Grimes exit(0); 249ea022d16SRodney W. Grimes } 250ea022d16SRodney W. Grimes } 2518ea31785SWarner Losh 2528ea31785SWarner Losh /* 2538ea31785SWarner Losh * Since we exit here, we should do that only after the above 2548ea31785SWarner Losh * recvfrom to keep inetd from constantly forking should there 2558ea31785SWarner Losh * be a problem. See the above comment about system clogging. 2568ea31785SWarner Losh */ 2578ea31785SWarner Losh if (chroot_dir) { 258a273f3aeSBill Fumerola if (ipchroot > 0) { 2591ed0e5d2SBill Fumerola char *tempchroot; 2601ed0e5d2SBill Fumerola struct stat sb; 2611ed0e5d2SBill Fumerola int statret; 2624dac6235SHajimu UMEMOTO struct sockaddr_storage ss; 2634dac6235SHajimu UMEMOTO char hbuf[NI_MAXHOST]; 2641ed0e5d2SBill Fumerola 2654dac6235SHajimu UMEMOTO memcpy(&ss, &from, from.ss_len); 2664dac6235SHajimu UMEMOTO unmappedaddr((struct sockaddr_in6 *)&ss); 2674dac6235SHajimu UMEMOTO getnameinfo((struct sockaddr *)&ss, ss.ss_len, 2684dac6235SHajimu UMEMOTO hbuf, sizeof(hbuf), NULL, 0, 2694f101318SHajimu UMEMOTO NI_NUMERICHOST); 2704dac6235SHajimu UMEMOTO asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); 271a273f3aeSBill Fumerola if (ipchroot == 2) 2721ed0e5d2SBill Fumerola statret = stat(tempchroot, &sb); 273a273f3aeSBill Fumerola if (ipchroot == 1 || 274a273f3aeSBill Fumerola (statret == 0 && (sb.st_mode & S_IFDIR))) 2751ed0e5d2SBill Fumerola chroot_dir = tempchroot; 2761ed0e5d2SBill Fumerola } 2778ea31785SWarner Losh /* Must get this before chroot because /etc might go away */ 278f62eaadfSGarrett Wollman if ((nobody = getpwnam(chuser)) == NULL) { 279f62eaadfSGarrett Wollman syslog(LOG_ERR, "%s: no such user", chuser); 2808ea31785SWarner Losh exit(1); 2818ea31785SWarner Losh } 2828ea31785SWarner Losh if (chroot(chroot_dir)) { 2838ea31785SWarner Losh syslog(LOG_ERR, "chroot: %s: %m", chroot_dir); 2848ea31785SWarner Losh exit(1); 2858ea31785SWarner Losh } 2868ea31785SWarner Losh chdir("/"); 28783c54719SDavid E. O'Brien setgroups(1, &nobody->pw_gid); 2880c90b488SChristian S.J. Peron setuid(nobody->pw_uid); 2898ea31785SWarner Losh } 2908ea31785SWarner Losh 2914dac6235SHajimu UMEMOTO len = sizeof(me); 2924dac6235SHajimu UMEMOTO if (getsockname(0, (struct sockaddr *)&me, &len) == 0) { 2934dac6235SHajimu UMEMOTO switch (me.ss_family) { 2944dac6235SHajimu UMEMOTO case AF_INET: 2954dac6235SHajimu UMEMOTO ((struct sockaddr_in *)&me)->sin_port = 0; 2964dac6235SHajimu UMEMOTO break; 2974dac6235SHajimu UMEMOTO case AF_INET6: 2984dac6235SHajimu UMEMOTO ((struct sockaddr_in6 *)&me)->sin6_port = 0; 2994dac6235SHajimu UMEMOTO break; 3004dac6235SHajimu UMEMOTO default: 3014dac6235SHajimu UMEMOTO /* unsupported */ 3024dac6235SHajimu UMEMOTO break; 3034dac6235SHajimu UMEMOTO } 3044dac6235SHajimu UMEMOTO } else { 3054dac6235SHajimu UMEMOTO memset(&me, 0, sizeof(me)); 3064dac6235SHajimu UMEMOTO me.ss_family = from.ss_family; 3074dac6235SHajimu UMEMOTO me.ss_len = from.ss_len; 3084dac6235SHajimu UMEMOTO } 309ea022d16SRodney W. Grimes alarm(0); 310ea022d16SRodney W. Grimes close(0); 311ea022d16SRodney W. Grimes close(1); 3124dac6235SHajimu UMEMOTO peer = socket(from.ss_family, SOCK_DGRAM, 0); 313ea022d16SRodney W. Grimes if (peer < 0) { 314a8faeabcSPhilippe Charnier syslog(LOG_ERR, "socket: %m"); 315ea022d16SRodney W. Grimes exit(1); 316ea022d16SRodney W. Grimes } 3174dac6235SHajimu UMEMOTO if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) { 318a8faeabcSPhilippe Charnier syslog(LOG_ERR, "bind: %m"); 319ea022d16SRodney W. Grimes exit(1); 320ea022d16SRodney W. Grimes } 3214dac6235SHajimu UMEMOTO if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) { 322a8faeabcSPhilippe Charnier syslog(LOG_ERR, "connect: %m"); 323ea022d16SRodney W. Grimes exit(1); 324ea022d16SRodney W. Grimes } 325ea022d16SRodney W. Grimes tp = (struct tftphdr *)buf; 326ea022d16SRodney W. Grimes tp->th_opcode = ntohs(tp->th_opcode); 327ea022d16SRodney W. Grimes if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 328ea022d16SRodney W. Grimes tftp(tp, n); 329ea022d16SRodney W. Grimes exit(1); 330ea022d16SRodney W. Grimes } 331ea022d16SRodney W. Grimes 3327bc7e0c8SBrian Somers static void 3337bc7e0c8SBrian Somers reduce_path(char *fn) 3347bc7e0c8SBrian Somers { 3357bc7e0c8SBrian Somers char *slash, *ptr; 3367bc7e0c8SBrian Somers 3377bc7e0c8SBrian Somers /* Reduce all "/+./" to "/" (just in case we've got "/./../" later */ 3387bc7e0c8SBrian Somers while ((slash = strstr(fn, "/./")) != NULL) { 3397bc7e0c8SBrian Somers for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 3407bc7e0c8SBrian Somers ; 3417bc7e0c8SBrian Somers slash += 2; 3427bc7e0c8SBrian Somers while (*slash) 3437bc7e0c8SBrian Somers *++ptr = *++slash; 3447bc7e0c8SBrian Somers } 3457bc7e0c8SBrian Somers 3467bc7e0c8SBrian Somers /* Now reduce all "/something/+../" to "/" */ 3477bc7e0c8SBrian Somers while ((slash = strstr(fn, "/../")) != NULL) { 3487bc7e0c8SBrian Somers if (slash == fn) 3497bc7e0c8SBrian Somers break; 3507bc7e0c8SBrian Somers for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) 3517bc7e0c8SBrian Somers ; 3527bc7e0c8SBrian Somers for (ptr--; ptr >= fn; ptr--) 3537bc7e0c8SBrian Somers if (*ptr == '/') 3547bc7e0c8SBrian Somers break; 3557bc7e0c8SBrian Somers if (ptr < fn) 3567bc7e0c8SBrian Somers break; 3577bc7e0c8SBrian Somers slash += 3; 3587bc7e0c8SBrian Somers while (*slash) 3597bc7e0c8SBrian Somers *++ptr = *++slash; 3607bc7e0c8SBrian Somers } 3617bc7e0c8SBrian Somers } 3627bc7e0c8SBrian Somers 363ea022d16SRodney W. Grimes struct formats; 364dc4c3024SWarner Losh int validate_access(char **, int); 365dc4c3024SWarner Losh void xmitfile(struct formats *); 366dc4c3024SWarner Losh void recvfile(struct formats *); 367ea022d16SRodney W. Grimes 368ea022d16SRodney W. Grimes struct formats { 369f49c0dc0SDavid Malone const char *f_mode; 370dc4c3024SWarner Losh int (*f_validate)(char **, int); 371dc4c3024SWarner Losh void (*f_send)(struct formats *); 372dc4c3024SWarner Losh void (*f_recv)(struct formats *); 373ea022d16SRodney W. Grimes int f_convert; 374ea022d16SRodney W. Grimes } formats[] = { 3758692ad46SDavid Greenman { "netascii", validate_access, xmitfile, recvfile, 1 }, 3768692ad46SDavid Greenman { "octet", validate_access, xmitfile, recvfile, 0 }, 377ea022d16SRodney W. Grimes #ifdef notdef 378ea022d16SRodney W. Grimes { "mail", validate_user, sendmail, recvmail, 1 }, 379ea022d16SRodney W. Grimes #endif 380f49c0dc0SDavid Malone { 0, NULL, NULL, NULL, 0 } 381ea022d16SRodney W. Grimes }; 382ea022d16SRodney W. Grimes 383c9374115SDavid E. O'Brien struct options { 384f49c0dc0SDavid Malone const char *o_type; 385c9374115SDavid E. O'Brien char *o_request; 386c9374115SDavid E. O'Brien int o_reply; /* turn into union if need be */ 387c9374115SDavid E. O'Brien } options[] = { 388f49c0dc0SDavid Malone { "tsize", NULL, 0 }, /* OPT_TSIZE */ 389f49c0dc0SDavid Malone { "timeout", NULL, 0 }, /* OPT_TIMEOUT */ 390f49c0dc0SDavid Malone { NULL, NULL, 0 } 391c9374115SDavid E. O'Brien }; 392c9374115SDavid E. O'Brien 393c9374115SDavid E. O'Brien enum opt_enum { 394c9374115SDavid E. O'Brien OPT_TSIZE = 0, 395c9374115SDavid E. O'Brien OPT_TIMEOUT, 396c9374115SDavid E. O'Brien }; 397c9374115SDavid E. O'Brien 398ea022d16SRodney W. Grimes /* 399ea022d16SRodney W. Grimes * Handle initial connection protocol. 400ea022d16SRodney W. Grimes */ 401ea022d16SRodney W. Grimes void 402dc4c3024SWarner Losh tftp(struct tftphdr *tp, int size) 403ea022d16SRodney W. Grimes { 404dc4c3024SWarner Losh char *cp; 405c9374115SDavid E. O'Brien int i, first = 1, has_options = 0, ecode; 406dc4c3024SWarner Losh struct formats *pf; 407c9374115SDavid E. O'Brien char *filename, *mode, *option, *ccp; 4084359d8a3SStefan Farfeleder char fnbuf[PATH_MAX]; 409ea022d16SRodney W. Grimes 4109e95548cSMaxim Sobolev cp = tp->th_stuff; 411ea022d16SRodney W. Grimes again: 412ea022d16SRodney W. Grimes while (cp < buf + size) { 413ea022d16SRodney W. Grimes if (*cp == '\0') 414ea022d16SRodney W. Grimes break; 415ea022d16SRodney W. Grimes cp++; 416ea022d16SRodney W. Grimes } 417ea022d16SRodney W. Grimes if (*cp != '\0') { 418ea022d16SRodney W. Grimes nak(EBADOP); 419ea022d16SRodney W. Grimes exit(1); 420ea022d16SRodney W. Grimes } 4219e95548cSMaxim Sobolev i = cp - tp->th_stuff; 4229e95548cSMaxim Sobolev if (i >= sizeof(fnbuf)) { 4239e95548cSMaxim Sobolev nak(EBADOP); 4249e95548cSMaxim Sobolev exit(1); 4259e95548cSMaxim Sobolev } 4269e95548cSMaxim Sobolev memcpy(fnbuf, tp->th_stuff, i); 4279e95548cSMaxim Sobolev fnbuf[i] = '\0'; 4287bc7e0c8SBrian Somers reduce_path(fnbuf); 4299e95548cSMaxim Sobolev filename = fnbuf; 430ea022d16SRodney W. Grimes if (first) { 431ea022d16SRodney W. Grimes mode = ++cp; 432ea022d16SRodney W. Grimes first = 0; 433ea022d16SRodney W. Grimes goto again; 434ea022d16SRodney W. Grimes } 435ea022d16SRodney W. Grimes for (cp = mode; *cp; cp++) 436ea022d16SRodney W. Grimes if (isupper(*cp)) 437ea022d16SRodney W. Grimes *cp = tolower(*cp); 438ea022d16SRodney W. Grimes for (pf = formats; pf->f_mode; pf++) 439ea022d16SRodney W. Grimes if (strcmp(pf->f_mode, mode) == 0) 440ea022d16SRodney W. Grimes break; 441ea022d16SRodney W. Grimes if (pf->f_mode == 0) { 442ea022d16SRodney W. Grimes nak(EBADOP); 443ea022d16SRodney W. Grimes exit(1); 444ea022d16SRodney W. Grimes } 445c9374115SDavid E. O'Brien while (++cp < buf + size) { 446c9374115SDavid E. O'Brien for (i = 2, ccp = cp; i > 0; ccp++) { 447c9374115SDavid E. O'Brien if (ccp >= buf + size) { 44814f0ab1cSBenno Rice /* 44914f0ab1cSBenno Rice * Don't reject the request, just stop trying 45014f0ab1cSBenno Rice * to parse the option and get on with it. 45114f0ab1cSBenno Rice * Some Apple Open Firmware versions have 45214f0ab1cSBenno Rice * trailing garbage on the end of otherwise 45314f0ab1cSBenno Rice * valid requests. 45414f0ab1cSBenno Rice */ 45514f0ab1cSBenno Rice goto option_fail; 456c9374115SDavid E. O'Brien } else if (*ccp == '\0') 457c9374115SDavid E. O'Brien i--; 458c9374115SDavid E. O'Brien } 459c9374115SDavid E. O'Brien for (option = cp; *cp; cp++) 460c9374115SDavid E. O'Brien if (isupper(*cp)) 461c9374115SDavid E. O'Brien *cp = tolower(*cp); 462c9374115SDavid E. O'Brien for (i = 0; options[i].o_type != NULL; i++) 463c9374115SDavid E. O'Brien if (strcmp(option, options[i].o_type) == 0) { 464c9374115SDavid E. O'Brien options[i].o_request = ++cp; 465c9374115SDavid E. O'Brien has_options = 1; 466c9374115SDavid E. O'Brien } 467c9374115SDavid E. O'Brien cp = ccp-1; 468c9374115SDavid E. O'Brien } 469c9374115SDavid E. O'Brien 47014f0ab1cSBenno Rice option_fail: 471c9374115SDavid E. O'Brien if (options[OPT_TIMEOUT].o_request) { 472c9374115SDavid E. O'Brien int to = atoi(options[OPT_TIMEOUT].o_request); 473c9374115SDavid E. O'Brien if (to < 1 || to > 255) { 474c9374115SDavid E. O'Brien nak(EBADOP); 475c9374115SDavid E. O'Brien exit(1); 476c9374115SDavid E. O'Brien } 477c9374115SDavid E. O'Brien else if (to <= max_rexmtval) 478c9374115SDavid E. O'Brien options[OPT_TIMEOUT].o_reply = rexmtval = to; 479c9374115SDavid E. O'Brien else 480c9374115SDavid E. O'Brien options[OPT_TIMEOUT].o_request = NULL; 481c9374115SDavid E. O'Brien } 482c9374115SDavid E. O'Brien 483ea022d16SRodney W. Grimes ecode = (*pf->f_validate)(&filename, tp->th_opcode); 4847bc7e0c8SBrian Somers if (has_options && ecode == 0) 485c9374115SDavid E. O'Brien oack(); 486ea022d16SRodney W. Grimes if (logging) { 4874dac6235SHajimu UMEMOTO char hbuf[NI_MAXHOST]; 48832af26a5SBrian Somers 4894dac6235SHajimu UMEMOTO getnameinfo((struct sockaddr *)&from, from.ss_len, 4904f101318SHajimu UMEMOTO hbuf, sizeof(hbuf), NULL, 0, 0); 4914dac6235SHajimu UMEMOTO syslog(LOG_INFO, "%s: %s request for %s: %s", hbuf, 492ea022d16SRodney W. Grimes tp->th_opcode == WRQ ? "write" : "read", 493ea022d16SRodney W. Grimes filename, errtomsg(ecode)); 494ea022d16SRodney W. Grimes } 495ea022d16SRodney W. Grimes if (ecode) { 496ea022d16SRodney W. Grimes /* 497ea022d16SRodney W. Grimes * Avoid storms of naks to a RRQ broadcast for a relative 498ea022d16SRodney W. Grimes * bootfile pathname from a diskless Sun. 499ea022d16SRodney W. Grimes */ 500ea022d16SRodney W. Grimes if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 501ea022d16SRodney W. Grimes exit(0); 502ea022d16SRodney W. Grimes nak(ecode); 503ea022d16SRodney W. Grimes exit(1); 504ea022d16SRodney W. Grimes } 505ea022d16SRodney W. Grimes if (tp->th_opcode == WRQ) 506ea022d16SRodney W. Grimes (*pf->f_recv)(pf); 507ea022d16SRodney W. Grimes else 508ea022d16SRodney W. Grimes (*pf->f_send)(pf); 509ea022d16SRodney W. Grimes exit(0); 510ea022d16SRodney W. Grimes } 511ea022d16SRodney W. Grimes 512ea022d16SRodney W. Grimes 513ea022d16SRodney W. Grimes FILE *file; 514ea022d16SRodney W. Grimes 515ea022d16SRodney W. Grimes /* 516ea022d16SRodney W. Grimes * Validate file access. Since we 517ea022d16SRodney W. Grimes * have no uid or gid, for now require 518ea022d16SRodney W. Grimes * file to exist and be publicly 519ea022d16SRodney W. Grimes * readable/writable. 520ea022d16SRodney W. Grimes * If we were invoked with arguments 521ea022d16SRodney W. Grimes * from inetd then the file must also be 522ea022d16SRodney W. Grimes * in one of the given directory prefixes. 523ea022d16SRodney W. Grimes * Note also, full path name must be 524ea022d16SRodney W. Grimes * given as we have no login directory. 525ea022d16SRodney W. Grimes */ 526ea022d16SRodney W. Grimes int 527dc4c3024SWarner Losh validate_access(char **filep, int mode) 528ea022d16SRodney W. Grimes { 529ea022d16SRodney W. Grimes struct stat stbuf; 530ea022d16SRodney W. Grimes int fd; 531ea022d16SRodney W. Grimes struct dirlist *dirp; 532ea022d16SRodney W. Grimes static char pathname[MAXPATHLEN]; 533ea022d16SRodney W. Grimes char *filename = *filep; 534ea022d16SRodney W. Grimes 535ea022d16SRodney W. Grimes /* 536ea022d16SRodney W. Grimes * Prevent tricksters from getting around the directory restrictions 537ea022d16SRodney W. Grimes */ 538ea022d16SRodney W. Grimes if (strstr(filename, "/../")) 539ea022d16SRodney W. Grimes return (EACCESS); 540ea022d16SRodney W. Grimes 541ea022d16SRodney W. Grimes if (*filename == '/') { 542ea022d16SRodney W. Grimes /* 543ea022d16SRodney W. Grimes * Allow the request if it's in one of the approved locations. 544ea022d16SRodney W. Grimes * Special case: check the null prefix ("/") by looking 545ea022d16SRodney W. Grimes * for length = 1 and relying on the arg. processing that 546ea022d16SRodney W. Grimes * it's a /. 547ea022d16SRodney W. Grimes */ 548ea022d16SRodney W. Grimes for (dirp = dirs; dirp->name != NULL; dirp++) { 549ea022d16SRodney W. Grimes if (dirp->len == 1 || 550ea022d16SRodney W. Grimes (!strncmp(filename, dirp->name, dirp->len) && 551ea022d16SRodney W. Grimes filename[dirp->len] == '/')) 552ea022d16SRodney W. Grimes break; 553ea022d16SRodney W. Grimes } 554ea022d16SRodney W. Grimes /* If directory list is empty, allow access to any file */ 555ea022d16SRodney W. Grimes if (dirp->name == NULL && dirp != dirs) 556ea022d16SRodney W. Grimes return (EACCESS); 557ea022d16SRodney W. Grimes if (stat(filename, &stbuf) < 0) 558ea022d16SRodney W. Grimes return (errno == ENOENT ? ENOTFOUND : EACCESS); 559ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IFMT) != S_IFREG) 560ea022d16SRodney W. Grimes return (ENOTFOUND); 561ea022d16SRodney W. Grimes if (mode == RRQ) { 562ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IROTH) == 0) 563ea022d16SRodney W. Grimes return (EACCESS); 564ea022d16SRodney W. Grimes } else { 565ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IWOTH) == 0) 566ea022d16SRodney W. Grimes return (EACCESS); 567ea022d16SRodney W. Grimes } 568ea022d16SRodney W. Grimes } else { 569ea022d16SRodney W. Grimes int err; 570ea022d16SRodney W. Grimes 571ea022d16SRodney W. Grimes /* 572ea022d16SRodney W. Grimes * Relative file name: search the approved locations for it. 57323adc6b8SJordan K. Hubbard * Don't allow write requests that avoid directory 574ea022d16SRodney W. Grimes * restrictions. 575ea022d16SRodney W. Grimes */ 576ea022d16SRodney W. Grimes 57723adc6b8SJordan K. Hubbard if (!strncmp(filename, "../", 3)) 578ea022d16SRodney W. Grimes return (EACCESS); 579ea022d16SRodney W. Grimes 580ea022d16SRodney W. Grimes /* 581ea022d16SRodney W. Grimes * If the file exists in one of the directories and isn't 582ea022d16SRodney W. Grimes * readable, continue looking. However, change the error code 583ea022d16SRodney W. Grimes * to give an indication that the file exists. 584ea022d16SRodney W. Grimes */ 585ea022d16SRodney W. Grimes err = ENOTFOUND; 586ea022d16SRodney W. Grimes for (dirp = dirs; dirp->name != NULL; dirp++) { 587fca08b7cSWarner Losh snprintf(pathname, sizeof(pathname), "%s/%s", 588fca08b7cSWarner Losh dirp->name, filename); 589ea022d16SRodney W. Grimes if (stat(pathname, &stbuf) == 0 && 590ea022d16SRodney W. Grimes (stbuf.st_mode & S_IFMT) == S_IFREG) { 591ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IROTH) != 0) { 592ea022d16SRodney W. Grimes break; 593ea022d16SRodney W. Grimes } 594ea022d16SRodney W. Grimes err = EACCESS; 595ea022d16SRodney W. Grimes } 596ea022d16SRodney W. Grimes } 597eff77877SMatthew N. Dodd if (dirp->name != NULL) 598ea022d16SRodney W. Grimes *filep = filename = pathname; 599eff77877SMatthew N. Dodd else if (mode == RRQ) 600eff77877SMatthew N. Dodd return (err); 601ea022d16SRodney W. Grimes } 602c9374115SDavid E. O'Brien if (options[OPT_TSIZE].o_request) { 603c9374115SDavid E. O'Brien if (mode == RRQ) 604c9374115SDavid E. O'Brien options[OPT_TSIZE].o_reply = stbuf.st_size; 605c9374115SDavid E. O'Brien else 606c9374115SDavid E. O'Brien /* XXX Allows writes of all sizes. */ 607c9374115SDavid E. O'Brien options[OPT_TSIZE].o_reply = 608c9374115SDavid E. O'Brien atoi(options[OPT_TSIZE].o_request); 609c9374115SDavid E. O'Brien } 610eff77877SMatthew N. Dodd if (mode == RRQ) 611eff77877SMatthew N. Dodd fd = open(filename, O_RDONLY); 612eff77877SMatthew N. Dodd else { 613eff77877SMatthew N. Dodd if (create_new) 614eff77877SMatthew N. Dodd fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0666); 615eff77877SMatthew N. Dodd else 616eff77877SMatthew N. Dodd fd = open(filename, O_WRONLY|O_TRUNC); 617eff77877SMatthew N. Dodd } 618ea022d16SRodney W. Grimes if (fd < 0) 619ea022d16SRodney W. Grimes return (errno + 100); 620ea022d16SRodney W. Grimes file = fdopen(fd, (mode == RRQ)? "r":"w"); 621ea022d16SRodney W. Grimes if (file == NULL) { 622e99c7b0dSMatthew N. Dodd close(fd); 623e99c7b0dSMatthew N. Dodd return (errno + 100); 624ea022d16SRodney W. Grimes } 625ea022d16SRodney W. Grimes return (0); 626ea022d16SRodney W. Grimes } 627ea022d16SRodney W. Grimes 628c9374115SDavid E. O'Brien int timeouts; 629ea022d16SRodney W. Grimes jmp_buf timeoutbuf; 630ea022d16SRodney W. Grimes 631ea022d16SRodney W. Grimes void 632dc4c3024SWarner Losh timer(int sig __unused) 633ea022d16SRodney W. Grimes { 634c9374115SDavid E. O'Brien if (++timeouts > MAX_TIMEOUTS) 635ea022d16SRodney W. Grimes exit(1); 636ea022d16SRodney W. Grimes longjmp(timeoutbuf, 1); 637ea022d16SRodney W. Grimes } 638ea022d16SRodney W. Grimes 639ea022d16SRodney W. Grimes /* 640ea022d16SRodney W. Grimes * Send the requested file. 641ea022d16SRodney W. Grimes */ 642ea022d16SRodney W. Grimes void 643dc4c3024SWarner Losh xmitfile(struct formats *pf) 644ea022d16SRodney W. Grimes { 645f49c0dc0SDavid Malone struct tftphdr *dp; 646dc4c3024SWarner Losh struct tftphdr *ap; /* ack packet */ 647dc4c3024SWarner Losh int size, n; 64867034ac6SJeroen Ruigrok van der Werven volatile unsigned short block; 649ea022d16SRodney W. Grimes 650ea022d16SRodney W. Grimes signal(SIGALRM, timer); 651ea022d16SRodney W. Grimes dp = r_init(); 652ea022d16SRodney W. Grimes ap = (struct tftphdr *)ackbuf; 653ea022d16SRodney W. Grimes block = 1; 654ea022d16SRodney W. Grimes do { 655ea022d16SRodney W. Grimes size = readit(file, &dp, pf->f_convert); 656ea022d16SRodney W. Grimes if (size < 0) { 657ea022d16SRodney W. Grimes nak(errno + 100); 658ea022d16SRodney W. Grimes goto abort; 659ea022d16SRodney W. Grimes } 660ea022d16SRodney W. Grimes dp->th_opcode = htons((u_short)DATA); 661ea022d16SRodney W. Grimes dp->th_block = htons((u_short)block); 662c9374115SDavid E. O'Brien timeouts = 0; 663ea022d16SRodney W. Grimes (void)setjmp(timeoutbuf); 664ea022d16SRodney W. Grimes 665ea022d16SRodney W. Grimes send_data: 666ff93f08cSDoug Ambrisko { 667ff93f08cSDoug Ambrisko int i, t = 1; 668ff93f08cSDoug Ambrisko for (i = 0; ; i++){ 669ea022d16SRodney W. Grimes if (send(peer, dp, size + 4, 0) != size + 4) { 670ff93f08cSDoug Ambrisko sleep(t); 671ff93f08cSDoug Ambrisko t = (t < 32) ? t<< 1 : t; 672ff93f08cSDoug Ambrisko if (i >= 12) { 673a8faeabcSPhilippe Charnier syslog(LOG_ERR, "write: %m"); 674ea022d16SRodney W. Grimes goto abort; 675ea022d16SRodney W. Grimes } 676ff93f08cSDoug Ambrisko } 677ff93f08cSDoug Ambrisko break; 678ff93f08cSDoug Ambrisko } 679ff93f08cSDoug Ambrisko } 680ea022d16SRodney W. Grimes read_ahead(file, pf->f_convert); 681ea022d16SRodney W. Grimes for ( ; ; ) { 682ea022d16SRodney W. Grimes alarm(rexmtval); /* read the ack */ 683ea022d16SRodney W. Grimes n = recv(peer, ackbuf, sizeof (ackbuf), 0); 684ea022d16SRodney W. Grimes alarm(0); 685ea022d16SRodney W. Grimes if (n < 0) { 686a8faeabcSPhilippe Charnier syslog(LOG_ERR, "read: %m"); 687ea022d16SRodney W. Grimes goto abort; 688ea022d16SRodney W. Grimes } 689ea022d16SRodney W. Grimes ap->th_opcode = ntohs((u_short)ap->th_opcode); 690ea022d16SRodney W. Grimes ap->th_block = ntohs((u_short)ap->th_block); 691ea022d16SRodney W. Grimes 692ea022d16SRodney W. Grimes if (ap->th_opcode == ERROR) 693ea022d16SRodney W. Grimes goto abort; 694ea022d16SRodney W. Grimes 695ea022d16SRodney W. Grimes if (ap->th_opcode == ACK) { 696ea022d16SRodney W. Grimes if (ap->th_block == block) 697ea022d16SRodney W. Grimes break; 698ea022d16SRodney W. Grimes /* Re-synchronize with the other side */ 699ea022d16SRodney W. Grimes (void) synchnet(peer); 700ea022d16SRodney W. Grimes if (ap->th_block == (block -1)) 701ea022d16SRodney W. Grimes goto send_data; 702ea022d16SRodney W. Grimes } 703ea022d16SRodney W. Grimes 704ea022d16SRodney W. Grimes } 705ea022d16SRodney W. Grimes block++; 706ea022d16SRodney W. Grimes } while (size == SEGSIZE); 707ea022d16SRodney W. Grimes abort: 708ea022d16SRodney W. Grimes (void) fclose(file); 709ea022d16SRodney W. Grimes } 710ea022d16SRodney W. Grimes 711ea022d16SRodney W. Grimes void 712dc4c3024SWarner Losh justquit(int sig __unused) 713ea022d16SRodney W. Grimes { 714ea022d16SRodney W. Grimes exit(0); 715ea022d16SRodney W. Grimes } 716ea022d16SRodney W. Grimes 717ea022d16SRodney W. Grimes 718ea022d16SRodney W. Grimes /* 719ea022d16SRodney W. Grimes * Receive a file. 720ea022d16SRodney W. Grimes */ 721ea022d16SRodney W. Grimes void 722dc4c3024SWarner Losh recvfile(struct formats *pf) 723ea022d16SRodney W. Grimes { 724f49c0dc0SDavid Malone struct tftphdr *dp; 725dc4c3024SWarner Losh struct tftphdr *ap; /* ack buffer */ 726dc4c3024SWarner Losh int n, size; 72767034ac6SJeroen Ruigrok van der Werven volatile unsigned short block; 728ea022d16SRodney W. Grimes 729ea022d16SRodney W. Grimes signal(SIGALRM, timer); 730ea022d16SRodney W. Grimes dp = w_init(); 731ea022d16SRodney W. Grimes ap = (struct tftphdr *)ackbuf; 732ea022d16SRodney W. Grimes block = 0; 733ea022d16SRodney W. Grimes do { 734c9374115SDavid E. O'Brien timeouts = 0; 735ea022d16SRodney W. Grimes ap->th_opcode = htons((u_short)ACK); 736ea022d16SRodney W. Grimes ap->th_block = htons((u_short)block); 737ea022d16SRodney W. Grimes block++; 738ea022d16SRodney W. Grimes (void) setjmp(timeoutbuf); 739ea022d16SRodney W. Grimes send_ack: 740ea022d16SRodney W. Grimes if (send(peer, ackbuf, 4, 0) != 4) { 741a8faeabcSPhilippe Charnier syslog(LOG_ERR, "write: %m"); 742ea022d16SRodney W. Grimes goto abort; 743ea022d16SRodney W. Grimes } 744ea022d16SRodney W. Grimes write_behind(file, pf->f_convert); 745ea022d16SRodney W. Grimes for ( ; ; ) { 746ea022d16SRodney W. Grimes alarm(rexmtval); 747ea022d16SRodney W. Grimes n = recv(peer, dp, PKTSIZE, 0); 748ea022d16SRodney W. Grimes alarm(0); 749ea022d16SRodney W. Grimes if (n < 0) { /* really? */ 750a8faeabcSPhilippe Charnier syslog(LOG_ERR, "read: %m"); 751ea022d16SRodney W. Grimes goto abort; 752ea022d16SRodney W. Grimes } 753ea022d16SRodney W. Grimes dp->th_opcode = ntohs((u_short)dp->th_opcode); 754ea022d16SRodney W. Grimes dp->th_block = ntohs((u_short)dp->th_block); 755ea022d16SRodney W. Grimes if (dp->th_opcode == ERROR) 756ea022d16SRodney W. Grimes goto abort; 757ea022d16SRodney W. Grimes if (dp->th_opcode == DATA) { 758ea022d16SRodney W. Grimes if (dp->th_block == block) { 759ea022d16SRodney W. Grimes break; /* normal */ 760ea022d16SRodney W. Grimes } 761ea022d16SRodney W. Grimes /* Re-synchronize with the other side */ 762ea022d16SRodney W. Grimes (void) synchnet(peer); 763ea022d16SRodney W. Grimes if (dp->th_block == (block-1)) 764ea022d16SRodney W. Grimes goto send_ack; /* rexmit */ 765ea022d16SRodney W. Grimes } 766ea022d16SRodney W. Grimes } 767ea022d16SRodney W. Grimes /* size = write(file, dp->th_data, n - 4); */ 768ea022d16SRodney W. Grimes size = writeit(file, &dp, n - 4, pf->f_convert); 769ea022d16SRodney W. Grimes if (size != (n-4)) { /* ahem */ 770ea022d16SRodney W. Grimes if (size < 0) nak(errno + 100); 771ea022d16SRodney W. Grimes else nak(ENOSPACE); 772ea022d16SRodney W. Grimes goto abort; 773ea022d16SRodney W. Grimes } 774ea022d16SRodney W. Grimes } while (size == SEGSIZE); 775ea022d16SRodney W. Grimes write_behind(file, pf->f_convert); 776ea022d16SRodney W. Grimes (void) fclose(file); /* close data file */ 777ea022d16SRodney W. Grimes 778ea022d16SRodney W. Grimes ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 779ea022d16SRodney W. Grimes ap->th_block = htons((u_short)(block)); 780ea022d16SRodney W. Grimes (void) send(peer, ackbuf, 4, 0); 781ea022d16SRodney W. Grimes 782ea022d16SRodney W. Grimes signal(SIGALRM, justquit); /* just quit on timeout */ 783ea022d16SRodney W. Grimes alarm(rexmtval); 784ea022d16SRodney W. Grimes n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 785ea022d16SRodney W. Grimes alarm(0); 786ea022d16SRodney W. Grimes if (n >= 4 && /* if read some data */ 787ea022d16SRodney W. Grimes dp->th_opcode == DATA && /* and got a data block */ 788ea022d16SRodney W. Grimes block == dp->th_block) { /* then my last ack was lost */ 789ea022d16SRodney W. Grimes (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 790ea022d16SRodney W. Grimes } 791ea022d16SRodney W. Grimes abort: 792ea022d16SRodney W. Grimes return; 793ea022d16SRodney W. Grimes } 794ea022d16SRodney W. Grimes 795ea022d16SRodney W. Grimes struct errmsg { 796ea022d16SRodney W. Grimes int e_code; 797f49c0dc0SDavid Malone const char *e_msg; 798ea022d16SRodney W. Grimes } errmsgs[] = { 799ea022d16SRodney W. Grimes { EUNDEF, "Undefined error code" }, 800ea022d16SRodney W. Grimes { ENOTFOUND, "File not found" }, 801ea022d16SRodney W. Grimes { EACCESS, "Access violation" }, 802ea022d16SRodney W. Grimes { ENOSPACE, "Disk full or allocation exceeded" }, 803ea022d16SRodney W. Grimes { EBADOP, "Illegal TFTP operation" }, 804ea022d16SRodney W. Grimes { EBADID, "Unknown transfer ID" }, 805ea022d16SRodney W. Grimes { EEXISTS, "File already exists" }, 806ea022d16SRodney W. Grimes { ENOUSER, "No such user" }, 807c9374115SDavid E. O'Brien { EOPTNEG, "Option negotiation" }, 808ea022d16SRodney W. Grimes { -1, 0 } 809ea022d16SRodney W. Grimes }; 810ea022d16SRodney W. Grimes 811f49c0dc0SDavid Malone static const char * 812dc4c3024SWarner Losh errtomsg(int error) 813ea022d16SRodney W. Grimes { 814f49c0dc0SDavid Malone static char ebuf[20]; 815dc4c3024SWarner Losh struct errmsg *pe; 816ea022d16SRodney W. Grimes if (error == 0) 817ea022d16SRodney W. Grimes return "success"; 818ea022d16SRodney W. Grimes for (pe = errmsgs; pe->e_code >= 0; pe++) 819ea022d16SRodney W. Grimes if (pe->e_code == error) 820ea022d16SRodney W. Grimes return pe->e_msg; 821f49c0dc0SDavid Malone snprintf(ebuf, sizeof(buf), "error %d", error); 822f49c0dc0SDavid Malone return ebuf; 823ea022d16SRodney W. Grimes } 824ea022d16SRodney W. Grimes 825ea022d16SRodney W. Grimes /* 826ea022d16SRodney W. Grimes * Send a nak packet (error message). 827ea022d16SRodney W. Grimes * Error code passed in is one of the 828ea022d16SRodney W. Grimes * standard TFTP codes, or a UNIX errno 829ea022d16SRodney W. Grimes * offset by 100. 830ea022d16SRodney W. Grimes */ 831ea022d16SRodney W. Grimes static void 832dc4c3024SWarner Losh nak(int error) 833ea022d16SRodney W. Grimes { 834dc4c3024SWarner Losh struct tftphdr *tp; 835ea022d16SRodney W. Grimes int length; 836dc4c3024SWarner Losh struct errmsg *pe; 837ea022d16SRodney W. Grimes 838ea022d16SRodney W. Grimes tp = (struct tftphdr *)buf; 839ea022d16SRodney W. Grimes tp->th_opcode = htons((u_short)ERROR); 840ea022d16SRodney W. Grimes tp->th_code = htons((u_short)error); 841ea022d16SRodney W. Grimes for (pe = errmsgs; pe->e_code >= 0; pe++) 842ea022d16SRodney W. Grimes if (pe->e_code == error) 843ea022d16SRodney W. Grimes break; 844ea022d16SRodney W. Grimes if (pe->e_code < 0) { 845ea022d16SRodney W. Grimes pe->e_msg = strerror(error - 100); 846ea022d16SRodney W. Grimes tp->th_code = EUNDEF; /* set 'undef' errorcode */ 847ea022d16SRodney W. Grimes } 848ea022d16SRodney W. Grimes strcpy(tp->th_msg, pe->e_msg); 849ea022d16SRodney W. Grimes length = strlen(pe->e_msg); 850ea022d16SRodney W. Grimes tp->th_msg[length] = '\0'; 851ea022d16SRodney W. Grimes length += 5; 852ea022d16SRodney W. Grimes if (send(peer, buf, length, 0) != length) 853a8faeabcSPhilippe Charnier syslog(LOG_ERR, "nak: %m"); 854ea022d16SRodney W. Grimes } 855c9374115SDavid E. O'Brien 8564dac6235SHajimu UMEMOTO /* translate IPv4 mapped IPv6 address to IPv4 address */ 8574dac6235SHajimu UMEMOTO static void 8584dac6235SHajimu UMEMOTO unmappedaddr(struct sockaddr_in6 *sin6) 8594dac6235SHajimu UMEMOTO { 8604dac6235SHajimu UMEMOTO struct sockaddr_in *sin4; 8614dac6235SHajimu UMEMOTO u_int32_t addr; 8624dac6235SHajimu UMEMOTO int port; 8634dac6235SHajimu UMEMOTO 8644dac6235SHajimu UMEMOTO if (sin6->sin6_family != AF_INET6 || 8654dac6235SHajimu UMEMOTO !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 8664dac6235SHajimu UMEMOTO return; 8674dac6235SHajimu UMEMOTO sin4 = (struct sockaddr_in *)sin6; 8684dac6235SHajimu UMEMOTO addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 8694dac6235SHajimu UMEMOTO port = sin6->sin6_port; 8704dac6235SHajimu UMEMOTO memset(sin4, 0, sizeof(struct sockaddr_in)); 8714dac6235SHajimu UMEMOTO sin4->sin_addr.s_addr = addr; 8724dac6235SHajimu UMEMOTO sin4->sin_port = port; 8734dac6235SHajimu UMEMOTO sin4->sin_family = AF_INET; 8744dac6235SHajimu UMEMOTO sin4->sin_len = sizeof(struct sockaddr_in); 8754dac6235SHajimu UMEMOTO } 8764dac6235SHajimu UMEMOTO 877c9374115SDavid E. O'Brien /* 878c9374115SDavid E. O'Brien * Send an oack packet (option acknowledgement). 879c9374115SDavid E. O'Brien */ 880c9374115SDavid E. O'Brien static void 881dc4c3024SWarner Losh oack(void) 882c9374115SDavid E. O'Brien { 883c9374115SDavid E. O'Brien struct tftphdr *tp, *ap; 884c9374115SDavid E. O'Brien int size, i, n; 885c9374115SDavid E. O'Brien char *bp; 886c9374115SDavid E. O'Brien 887c9374115SDavid E. O'Brien tp = (struct tftphdr *)buf; 888c9374115SDavid E. O'Brien bp = buf + 2; 889c9374115SDavid E. O'Brien size = sizeof(buf) - 2; 890c9374115SDavid E. O'Brien tp->th_opcode = htons((u_short)OACK); 891c9374115SDavid E. O'Brien for (i = 0; options[i].o_type != NULL; i++) { 892c9374115SDavid E. O'Brien if (options[i].o_request) { 893c9374115SDavid E. O'Brien n = snprintf(bp, size, "%s%c%d", options[i].o_type, 894c9374115SDavid E. O'Brien 0, options[i].o_reply); 895c9374115SDavid E. O'Brien bp += n+1; 896c9374115SDavid E. O'Brien size -= n+1; 897c9374115SDavid E. O'Brien if (size < 0) { 898c9374115SDavid E. O'Brien syslog(LOG_ERR, "oack: buffer overflow"); 899c9374115SDavid E. O'Brien exit(1); 900c9374115SDavid E. O'Brien } 901c9374115SDavid E. O'Brien } 902c9374115SDavid E. O'Brien } 903c9374115SDavid E. O'Brien size = bp - buf; 904c9374115SDavid E. O'Brien ap = (struct tftphdr *)ackbuf; 905c9374115SDavid E. O'Brien signal(SIGALRM, timer); 906c9374115SDavid E. O'Brien timeouts = 0; 907c9374115SDavid E. O'Brien 908c9374115SDavid E. O'Brien (void)setjmp(timeoutbuf); 909c9374115SDavid E. O'Brien if (send(peer, buf, size, 0) != size) { 910c9374115SDavid E. O'Brien syslog(LOG_INFO, "oack: %m"); 911c9374115SDavid E. O'Brien exit(1); 912c9374115SDavid E. O'Brien } 913c9374115SDavid E. O'Brien 914c9374115SDavid E. O'Brien for (;;) { 915c9374115SDavid E. O'Brien alarm(rexmtval); 916c9374115SDavid E. O'Brien n = recv(peer, ackbuf, sizeof (ackbuf), 0); 917c9374115SDavid E. O'Brien alarm(0); 918c9374115SDavid E. O'Brien if (n < 0) { 919c9374115SDavid E. O'Brien syslog(LOG_ERR, "recv: %m"); 920c9374115SDavid E. O'Brien exit(1); 921c9374115SDavid E. O'Brien } 922c9374115SDavid E. O'Brien ap->th_opcode = ntohs((u_short)ap->th_opcode); 923c9374115SDavid E. O'Brien ap->th_block = ntohs((u_short)ap->th_block); 924c9374115SDavid E. O'Brien if (ap->th_opcode == ERROR) 925c9374115SDavid E. O'Brien exit(1); 926c9374115SDavid E. O'Brien if (ap->th_opcode == ACK && ap->th_block == 0) 927c9374115SDavid E. O'Brien break; 928c9374115SDavid E. O'Brien } 929c9374115SDavid E. O'Brien } 930