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> 60ea022d16SRodney W. Grimes 61ea022d16SRodney W. Grimes #include <netinet/in.h> 62ea022d16SRodney W. Grimes #include <arpa/tftp.h> 63ea022d16SRodney W. Grimes #include <arpa/inet.h> 64ea022d16SRodney W. Grimes 65ea022d16SRodney W. Grimes #include <ctype.h> 66ea022d16SRodney W. Grimes #include <errno.h> 67ea022d16SRodney W. Grimes #include <fcntl.h> 6832af26a5SBrian Somers #include <libutil.h> 69ea022d16SRodney W. Grimes #include <netdb.h> 70a8faeabcSPhilippe Charnier #include <pwd.h> 71ea022d16SRodney W. Grimes #include <setjmp.h> 72ea022d16SRodney W. Grimes #include <signal.h> 73ea022d16SRodney W. Grimes #include <stdio.h> 74ea022d16SRodney W. Grimes #include <stdlib.h> 75ea022d16SRodney W. Grimes #include <string.h> 76ea022d16SRodney W. Grimes #include <syslog.h> 77ea022d16SRodney W. Grimes #include <unistd.h> 78ea022d16SRodney W. Grimes 79ea022d16SRodney W. Grimes #include "tftpsubs.h" 80ea022d16SRodney W. Grimes 81ea022d16SRodney W. Grimes #define TIMEOUT 5 82c9374115SDavid E. O'Brien #define MAX_TIMEOUTS 5 83ea022d16SRodney W. Grimes 84ea022d16SRodney W. Grimes int peer; 85ea022d16SRodney W. Grimes int rexmtval = TIMEOUT; 86c9374115SDavid E. O'Brien int max_rexmtval = 2*TIMEOUT; 87ea022d16SRodney W. Grimes 88ea022d16SRodney W. Grimes #define PKTSIZE SEGSIZE+4 89ea022d16SRodney W. Grimes char buf[PKTSIZE]; 90ea022d16SRodney W. Grimes char ackbuf[PKTSIZE]; 914dac6235SHajimu UMEMOTO struct sockaddr_storage from; 92ea022d16SRodney W. Grimes int fromlen; 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; 126dc4c3024SWarner Losh int n; 127ea022d16SRodney W. Grimes int ch, on; 1284dac6235SHajimu UMEMOTO struct sockaddr_storage me; 1294dac6235SHajimu UMEMOTO int len; 1308ea31785SWarner Losh char *chroot_dir = NULL; 1318ea31785SWarner Losh struct passwd *nobody; 132f49c0dc0SDavid Malone const char *chuser = "nobody"; 133ea022d16SRodney W. Grimes 13420ef8838SPoul-Henning Kamp openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 135eff77877SMatthew N. Dodd while ((ch = getopt(argc, argv, "cClns:u:Uw")) != -1) { 136ea022d16SRodney W. Grimes switch (ch) { 1371ed0e5d2SBill Fumerola case 'c': 1381ed0e5d2SBill Fumerola ipchroot = 1; 1391ed0e5d2SBill Fumerola break; 1401ed0e5d2SBill Fumerola case 'C': 1411ed0e5d2SBill Fumerola ipchroot = 2; 1421ed0e5d2SBill Fumerola break; 143ea022d16SRodney W. Grimes case 'l': 144ea022d16SRodney W. Grimes logging = 1; 145ea022d16SRodney W. Grimes break; 146ea022d16SRodney W. Grimes case 'n': 147ea022d16SRodney W. Grimes suppress_naks = 1; 148ea022d16SRodney W. Grimes break; 1498ea31785SWarner Losh case 's': 1508ea31785SWarner Losh chroot_dir = optarg; 1518ea31785SWarner Losh break; 152f62eaadfSGarrett Wollman case 'u': 153f62eaadfSGarrett Wollman chuser = optarg; 154f62eaadfSGarrett Wollman break; 155eff77877SMatthew N. Dodd case 'U': 156eff77877SMatthew N. Dodd mask = strtol(optarg, NULL, 0); 157eff77877SMatthew N. Dodd break; 158eff77877SMatthew N. Dodd case 'w': 159eff77877SMatthew N. Dodd create_new = 1; 160eff77877SMatthew N. Dodd break; 161ea022d16SRodney W. Grimes default: 162ea022d16SRodney W. Grimes syslog(LOG_WARNING, "ignoring unknown option -%c", ch); 163ea022d16SRodney W. Grimes } 164ea022d16SRodney W. Grimes } 165ea022d16SRodney W. Grimes if (optind < argc) { 166ea022d16SRodney W. Grimes struct dirlist *dirp; 167ea022d16SRodney W. Grimes 168ea022d16SRodney W. Grimes /* Get list of directory prefixes. Skip relative pathnames. */ 169ea022d16SRodney W. Grimes for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; 170ea022d16SRodney W. Grimes optind++) { 171ea022d16SRodney W. Grimes if (argv[optind][0] == '/') { 172ea022d16SRodney W. Grimes dirp->name = argv[optind]; 173ea022d16SRodney W. Grimes dirp->len = strlen(dirp->name); 174ea022d16SRodney W. Grimes dirp++; 175ea022d16SRodney W. Grimes } 176ea022d16SRodney W. Grimes } 177ea022d16SRodney W. Grimes } 1788ea31785SWarner Losh else if (chroot_dir) { 1798ea31785SWarner Losh dirs->name = "/"; 1808ea31785SWarner Losh dirs->len = 1; 1818ea31785SWarner Losh } 182a273f3aeSBill Fumerola if (ipchroot > 0 && chroot_dir == NULL) { 1831ed0e5d2SBill Fumerola syslog(LOG_ERR, "-c requires -s"); 1841ed0e5d2SBill Fumerola exit(1); 1851ed0e5d2SBill Fumerola } 186ea022d16SRodney W. Grimes 187eff77877SMatthew N. Dodd umask(mask); 188eff77877SMatthew N. Dodd 189ea022d16SRodney W. Grimes on = 1; 190ea022d16SRodney W. Grimes if (ioctl(0, FIONBIO, &on) < 0) { 191a8faeabcSPhilippe Charnier syslog(LOG_ERR, "ioctl(FIONBIO): %m"); 192ea022d16SRodney W. Grimes exit(1); 193ea022d16SRodney W. Grimes } 194ea022d16SRodney W. Grimes fromlen = sizeof (from); 195ea022d16SRodney W. Grimes n = recvfrom(0, buf, sizeof (buf), 0, 196ea022d16SRodney W. Grimes (struct sockaddr *)&from, &fromlen); 197ea022d16SRodney W. Grimes if (n < 0) { 198a8faeabcSPhilippe Charnier syslog(LOG_ERR, "recvfrom: %m"); 199ea022d16SRodney W. Grimes exit(1); 200ea022d16SRodney W. Grimes } 201ea022d16SRodney W. Grimes /* 202ea022d16SRodney W. Grimes * Now that we have read the message out of the UDP 203ea022d16SRodney W. Grimes * socket, we fork and exit. Thus, inetd will go back 204ea022d16SRodney W. Grimes * to listening to the tftp port, and the next request 205ea022d16SRodney W. Grimes * to come in will start up a new instance of tftpd. 206ea022d16SRodney W. Grimes * 207ea022d16SRodney W. Grimes * We do this so that inetd can run tftpd in "wait" mode. 208ea022d16SRodney W. Grimes * The problem with tftpd running in "nowait" mode is that 209ea022d16SRodney W. Grimes * inetd may get one or more successful "selects" on the 210ea022d16SRodney W. Grimes * tftp port before we do our receive, so more than one 211ea022d16SRodney W. Grimes * instance of tftpd may be started up. Worse, if tftpd 212ea022d16SRodney W. Grimes * break before doing the above "recvfrom", inetd would 213ea022d16SRodney W. Grimes * spawn endless instances, clogging the system. 214ea022d16SRodney W. Grimes */ 215ea022d16SRodney W. Grimes { 216ea022d16SRodney W. Grimes int pid; 217ea022d16SRodney W. Grimes int i, j; 218ea022d16SRodney W. Grimes 219ea022d16SRodney W. Grimes for (i = 1; i < 20; i++) { 220ea022d16SRodney W. Grimes pid = fork(); 221ea022d16SRodney W. Grimes if (pid < 0) { 222ea022d16SRodney W. Grimes sleep(i); 223ea022d16SRodney W. Grimes /* 224ea022d16SRodney W. Grimes * flush out to most recently sent request. 225ea022d16SRodney W. Grimes * 226ea022d16SRodney W. Grimes * This may drop some request, but those 227ea022d16SRodney W. Grimes * will be resent by the clients when 228ea022d16SRodney W. Grimes * they timeout. The positive effect of 229ea022d16SRodney W. Grimes * this flush is to (try to) prevent more 230ea022d16SRodney W. Grimes * than one tftpd being started up to service 231ea022d16SRodney W. Grimes * a single request from a single client. 232ea022d16SRodney W. Grimes */ 233ea022d16SRodney W. Grimes j = sizeof from; 234ea022d16SRodney W. Grimes i = recvfrom(0, buf, sizeof (buf), 0, 235ea022d16SRodney W. Grimes (struct sockaddr *)&from, &j); 236ea022d16SRodney W. Grimes if (i > 0) { 237ea022d16SRodney W. Grimes n = i; 238ea022d16SRodney W. Grimes fromlen = j; 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, 2694dac6235SHajimu UMEMOTO NI_NUMERICHOST | NI_WITHSCOPEID); 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( "/" ); 2878ea31785SWarner Losh setuid(nobody->pw_uid); 28883c54719SDavid E. O'Brien setgroups(1, &nobody->pw_gid); 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 332ea022d16SRodney W. Grimes struct formats; 333dc4c3024SWarner Losh int validate_access(char **, int); 334dc4c3024SWarner Losh void xmitfile(struct formats *); 335dc4c3024SWarner Losh void recvfile(struct formats *); 336ea022d16SRodney W. Grimes 337ea022d16SRodney W. Grimes struct formats { 338f49c0dc0SDavid Malone const char *f_mode; 339dc4c3024SWarner Losh int (*f_validate)(char **, int); 340dc4c3024SWarner Losh void (*f_send)(struct formats *); 341dc4c3024SWarner Losh void (*f_recv)(struct formats *); 342ea022d16SRodney W. Grimes int f_convert; 343ea022d16SRodney W. Grimes } formats[] = { 3448692ad46SDavid Greenman { "netascii", validate_access, xmitfile, recvfile, 1 }, 3458692ad46SDavid Greenman { "octet", validate_access, xmitfile, recvfile, 0 }, 346ea022d16SRodney W. Grimes #ifdef notdef 347ea022d16SRodney W. Grimes { "mail", validate_user, sendmail, recvmail, 1 }, 348ea022d16SRodney W. Grimes #endif 349f49c0dc0SDavid Malone { 0, NULL, NULL, NULL, 0 } 350ea022d16SRodney W. Grimes }; 351ea022d16SRodney W. Grimes 352c9374115SDavid E. O'Brien struct options { 353f49c0dc0SDavid Malone const char *o_type; 354c9374115SDavid E. O'Brien char *o_request; 355c9374115SDavid E. O'Brien int o_reply; /* turn into union if need be */ 356c9374115SDavid E. O'Brien } options[] = { 357f49c0dc0SDavid Malone { "tsize", NULL, 0 }, /* OPT_TSIZE */ 358f49c0dc0SDavid Malone { "timeout", NULL, 0 }, /* OPT_TIMEOUT */ 359f49c0dc0SDavid Malone { NULL, NULL, 0 } 360c9374115SDavid E. O'Brien }; 361c9374115SDavid E. O'Brien 362c9374115SDavid E. O'Brien enum opt_enum { 363c9374115SDavid E. O'Brien OPT_TSIZE = 0, 364c9374115SDavid E. O'Brien OPT_TIMEOUT, 365c9374115SDavid E. O'Brien }; 366c9374115SDavid E. O'Brien 367ea022d16SRodney W. Grimes /* 368ea022d16SRodney W. Grimes * Handle initial connection protocol. 369ea022d16SRodney W. Grimes */ 370ea022d16SRodney W. Grimes void 371dc4c3024SWarner Losh tftp(struct tftphdr *tp, int size) 372ea022d16SRodney W. Grimes { 373dc4c3024SWarner Losh char *cp; 374c9374115SDavid E. O'Brien int i, first = 1, has_options = 0, ecode; 375dc4c3024SWarner Losh struct formats *pf; 376c9374115SDavid E. O'Brien char *filename, *mode, *option, *ccp; 3779e95548cSMaxim Sobolev char fnbuf[MAXPATHLEN]; 378ea022d16SRodney W. Grimes 3799e95548cSMaxim Sobolev cp = tp->th_stuff; 380ea022d16SRodney W. Grimes again: 381ea022d16SRodney W. Grimes while (cp < buf + size) { 382ea022d16SRodney W. Grimes if (*cp == '\0') 383ea022d16SRodney W. Grimes break; 384ea022d16SRodney W. Grimes cp++; 385ea022d16SRodney W. Grimes } 386ea022d16SRodney W. Grimes if (*cp != '\0') { 387ea022d16SRodney W. Grimes nak(EBADOP); 388ea022d16SRodney W. Grimes exit(1); 389ea022d16SRodney W. Grimes } 3909e95548cSMaxim Sobolev i = cp - tp->th_stuff; 3919e95548cSMaxim Sobolev if (i >= sizeof(fnbuf)) { 3929e95548cSMaxim Sobolev nak(EBADOP); 3939e95548cSMaxim Sobolev exit(1); 3949e95548cSMaxim Sobolev } 3959e95548cSMaxim Sobolev memcpy(fnbuf, tp->th_stuff, i); 3969e95548cSMaxim Sobolev fnbuf[i] = '\0'; 3979e95548cSMaxim Sobolev filename = fnbuf; 398ea022d16SRodney W. Grimes if (first) { 399ea022d16SRodney W. Grimes mode = ++cp; 400ea022d16SRodney W. Grimes first = 0; 401ea022d16SRodney W. Grimes goto again; 402ea022d16SRodney W. Grimes } 403ea022d16SRodney W. Grimes for (cp = mode; *cp; cp++) 404ea022d16SRodney W. Grimes if (isupper(*cp)) 405ea022d16SRodney W. Grimes *cp = tolower(*cp); 406ea022d16SRodney W. Grimes for (pf = formats; pf->f_mode; pf++) 407ea022d16SRodney W. Grimes if (strcmp(pf->f_mode, mode) == 0) 408ea022d16SRodney W. Grimes break; 409ea022d16SRodney W. Grimes if (pf->f_mode == 0) { 410ea022d16SRodney W. Grimes nak(EBADOP); 411ea022d16SRodney W. Grimes exit(1); 412ea022d16SRodney W. Grimes } 413c9374115SDavid E. O'Brien while (++cp < buf + size) { 414c9374115SDavid E. O'Brien for (i = 2, ccp = cp; i > 0; ccp++) { 415c9374115SDavid E. O'Brien if (ccp >= buf + size) { 41614f0ab1cSBenno Rice /* 41714f0ab1cSBenno Rice * Don't reject the request, just stop trying 41814f0ab1cSBenno Rice * to parse the option and get on with it. 41914f0ab1cSBenno Rice * Some Apple OpenFirmware versions have 42014f0ab1cSBenno Rice * trailing garbage on the end of otherwise 42114f0ab1cSBenno Rice * valid requests. 42214f0ab1cSBenno Rice */ 42314f0ab1cSBenno Rice goto option_fail; 424c9374115SDavid E. O'Brien } else if (*ccp == '\0') 425c9374115SDavid E. O'Brien i--; 426c9374115SDavid E. O'Brien } 427c9374115SDavid E. O'Brien for (option = cp; *cp; cp++) 428c9374115SDavid E. O'Brien if (isupper(*cp)) 429c9374115SDavid E. O'Brien *cp = tolower(*cp); 430c9374115SDavid E. O'Brien for (i = 0; options[i].o_type != NULL; i++) 431c9374115SDavid E. O'Brien if (strcmp(option, options[i].o_type) == 0) { 432c9374115SDavid E. O'Brien options[i].o_request = ++cp; 433c9374115SDavid E. O'Brien has_options = 1; 434c9374115SDavid E. O'Brien } 435c9374115SDavid E. O'Brien cp = ccp-1; 436c9374115SDavid E. O'Brien } 437c9374115SDavid E. O'Brien 43814f0ab1cSBenno Rice option_fail: 439c9374115SDavid E. O'Brien if (options[OPT_TIMEOUT].o_request) { 440c9374115SDavid E. O'Brien int to = atoi(options[OPT_TIMEOUT].o_request); 441c9374115SDavid E. O'Brien if (to < 1 || to > 255) { 442c9374115SDavid E. O'Brien nak(EBADOP); 443c9374115SDavid E. O'Brien exit(1); 444c9374115SDavid E. O'Brien } 445c9374115SDavid E. O'Brien else if (to <= max_rexmtval) 446c9374115SDavid E. O'Brien options[OPT_TIMEOUT].o_reply = rexmtval = to; 447c9374115SDavid E. O'Brien else 448c9374115SDavid E. O'Brien options[OPT_TIMEOUT].o_request = NULL; 449c9374115SDavid E. O'Brien } 450c9374115SDavid E. O'Brien 451ea022d16SRodney W. Grimes ecode = (*pf->f_validate)(&filename, tp->th_opcode); 452c9374115SDavid E. O'Brien if (has_options) 453c9374115SDavid E. O'Brien oack(); 454ea022d16SRodney W. Grimes if (logging) { 4554dac6235SHajimu UMEMOTO char hbuf[NI_MAXHOST]; 45632af26a5SBrian Somers 4574dac6235SHajimu UMEMOTO getnameinfo((struct sockaddr *)&from, from.ss_len, 4584dac6235SHajimu UMEMOTO hbuf, sizeof(hbuf), NULL, 0, 4594dac6235SHajimu UMEMOTO NI_WITHSCOPEID); 4604dac6235SHajimu UMEMOTO syslog(LOG_INFO, "%s: %s request for %s: %s", hbuf, 461ea022d16SRodney W. Grimes tp->th_opcode == WRQ ? "write" : "read", 462ea022d16SRodney W. Grimes filename, errtomsg(ecode)); 463ea022d16SRodney W. Grimes } 464ea022d16SRodney W. Grimes if (ecode) { 465ea022d16SRodney W. Grimes /* 466ea022d16SRodney W. Grimes * Avoid storms of naks to a RRQ broadcast for a relative 467ea022d16SRodney W. Grimes * bootfile pathname from a diskless Sun. 468ea022d16SRodney W. Grimes */ 469ea022d16SRodney W. Grimes if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 470ea022d16SRodney W. Grimes exit(0); 471ea022d16SRodney W. Grimes nak(ecode); 472ea022d16SRodney W. Grimes exit(1); 473ea022d16SRodney W. Grimes } 474ea022d16SRodney W. Grimes if (tp->th_opcode == WRQ) 475ea022d16SRodney W. Grimes (*pf->f_recv)(pf); 476ea022d16SRodney W. Grimes else 477ea022d16SRodney W. Grimes (*pf->f_send)(pf); 478ea022d16SRodney W. Grimes exit(0); 479ea022d16SRodney W. Grimes } 480ea022d16SRodney W. Grimes 481ea022d16SRodney W. Grimes 482ea022d16SRodney W. Grimes FILE *file; 483ea022d16SRodney W. Grimes 484ea022d16SRodney W. Grimes /* 485ea022d16SRodney W. Grimes * Validate file access. Since we 486ea022d16SRodney W. Grimes * have no uid or gid, for now require 487ea022d16SRodney W. Grimes * file to exist and be publicly 488ea022d16SRodney W. Grimes * readable/writable. 489ea022d16SRodney W. Grimes * If we were invoked with arguments 490ea022d16SRodney W. Grimes * from inetd then the file must also be 491ea022d16SRodney W. Grimes * in one of the given directory prefixes. 492ea022d16SRodney W. Grimes * Note also, full path name must be 493ea022d16SRodney W. Grimes * given as we have no login directory. 494ea022d16SRodney W. Grimes */ 495ea022d16SRodney W. Grimes int 496dc4c3024SWarner Losh validate_access(char **filep, int mode) 497ea022d16SRodney W. Grimes { 498ea022d16SRodney W. Grimes struct stat stbuf; 499ea022d16SRodney W. Grimes int fd; 500ea022d16SRodney W. Grimes struct dirlist *dirp; 501ea022d16SRodney W. Grimes static char pathname[MAXPATHLEN]; 502ea022d16SRodney W. Grimes char *filename = *filep; 503ea022d16SRodney W. Grimes 504ea022d16SRodney W. Grimes /* 505ea022d16SRodney W. Grimes * Prevent tricksters from getting around the directory restrictions 506ea022d16SRodney W. Grimes */ 507ea022d16SRodney W. Grimes if (strstr(filename, "/../")) 508ea022d16SRodney W. Grimes return (EACCESS); 509ea022d16SRodney W. Grimes 510ea022d16SRodney W. Grimes if (*filename == '/') { 511ea022d16SRodney W. Grimes /* 512ea022d16SRodney W. Grimes * Allow the request if it's in one of the approved locations. 513ea022d16SRodney W. Grimes * Special case: check the null prefix ("/") by looking 514ea022d16SRodney W. Grimes * for length = 1 and relying on the arg. processing that 515ea022d16SRodney W. Grimes * it's a /. 516ea022d16SRodney W. Grimes */ 517ea022d16SRodney W. Grimes for (dirp = dirs; dirp->name != NULL; dirp++) { 518ea022d16SRodney W. Grimes if (dirp->len == 1 || 519ea022d16SRodney W. Grimes (!strncmp(filename, dirp->name, dirp->len) && 520ea022d16SRodney W. Grimes filename[dirp->len] == '/')) 521ea022d16SRodney W. Grimes break; 522ea022d16SRodney W. Grimes } 523ea022d16SRodney W. Grimes /* If directory list is empty, allow access to any file */ 524ea022d16SRodney W. Grimes if (dirp->name == NULL && dirp != dirs) 525ea022d16SRodney W. Grimes return (EACCESS); 526ea022d16SRodney W. Grimes if (stat(filename, &stbuf) < 0) 527ea022d16SRodney W. Grimes return (errno == ENOENT ? ENOTFOUND : EACCESS); 528ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IFMT) != S_IFREG) 529ea022d16SRodney W. Grimes return (ENOTFOUND); 530ea022d16SRodney W. Grimes if (mode == RRQ) { 531ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IROTH) == 0) 532ea022d16SRodney W. Grimes return (EACCESS); 533ea022d16SRodney W. Grimes } else { 534ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IWOTH) == 0) 535ea022d16SRodney W. Grimes return (EACCESS); 536ea022d16SRodney W. Grimes } 537ea022d16SRodney W. Grimes } else { 538ea022d16SRodney W. Grimes int err; 539ea022d16SRodney W. Grimes 540ea022d16SRodney W. Grimes /* 541ea022d16SRodney W. Grimes * Relative file name: search the approved locations for it. 54223adc6b8SJordan K. Hubbard * Don't allow write requests that avoid directory 543ea022d16SRodney W. Grimes * restrictions. 544ea022d16SRodney W. Grimes */ 545ea022d16SRodney W. Grimes 54623adc6b8SJordan K. Hubbard if (!strncmp(filename, "../", 3)) 547ea022d16SRodney W. Grimes return (EACCESS); 548ea022d16SRodney W. Grimes 549ea022d16SRodney W. Grimes /* 550ea022d16SRodney W. Grimes * If the file exists in one of the directories and isn't 551ea022d16SRodney W. Grimes * readable, continue looking. However, change the error code 552ea022d16SRodney W. Grimes * to give an indication that the file exists. 553ea022d16SRodney W. Grimes */ 554ea022d16SRodney W. Grimes err = ENOTFOUND; 555ea022d16SRodney W. Grimes for (dirp = dirs; dirp->name != NULL; dirp++) { 556fca08b7cSWarner Losh snprintf(pathname, sizeof(pathname), "%s/%s", 557fca08b7cSWarner Losh dirp->name, filename); 558ea022d16SRodney W. Grimes if (stat(pathname, &stbuf) == 0 && 559ea022d16SRodney W. Grimes (stbuf.st_mode & S_IFMT) == S_IFREG) { 560ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IROTH) != 0) { 561ea022d16SRodney W. Grimes break; 562ea022d16SRodney W. Grimes } 563ea022d16SRodney W. Grimes err = EACCESS; 564ea022d16SRodney W. Grimes } 565ea022d16SRodney W. Grimes } 566eff77877SMatthew N. Dodd if (dirp->name != NULL) 567ea022d16SRodney W. Grimes *filep = filename = pathname; 568eff77877SMatthew N. Dodd else if (mode == RRQ) 569eff77877SMatthew N. Dodd return (err); 570ea022d16SRodney W. Grimes } 571c9374115SDavid E. O'Brien if (options[OPT_TSIZE].o_request) { 572c9374115SDavid E. O'Brien if (mode == RRQ) 573c9374115SDavid E. O'Brien options[OPT_TSIZE].o_reply = stbuf.st_size; 574c9374115SDavid E. O'Brien else 575c9374115SDavid E. O'Brien /* XXX Allows writes of all sizes. */ 576c9374115SDavid E. O'Brien options[OPT_TSIZE].o_reply = 577c9374115SDavid E. O'Brien atoi(options[OPT_TSIZE].o_request); 578c9374115SDavid E. O'Brien } 579eff77877SMatthew N. Dodd if (mode == RRQ) 580eff77877SMatthew N. Dodd fd = open(filename, O_RDONLY); 581eff77877SMatthew N. Dodd else { 582eff77877SMatthew N. Dodd if (create_new) 583eff77877SMatthew N. Dodd fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0666); 584eff77877SMatthew N. Dodd else 585eff77877SMatthew N. Dodd fd = open(filename, O_WRONLY|O_TRUNC); 586eff77877SMatthew N. Dodd } 587ea022d16SRodney W. Grimes if (fd < 0) 588ea022d16SRodney W. Grimes return (errno + 100); 589ea022d16SRodney W. Grimes file = fdopen(fd, (mode == RRQ)? "r":"w"); 590ea022d16SRodney W. Grimes if (file == NULL) { 591e99c7b0dSMatthew N. Dodd close(fd); 592e99c7b0dSMatthew N. Dodd return (errno + 100); 593ea022d16SRodney W. Grimes } 594ea022d16SRodney W. Grimes return (0); 595ea022d16SRodney W. Grimes } 596ea022d16SRodney W. Grimes 597c9374115SDavid E. O'Brien int timeouts; 598ea022d16SRodney W. Grimes jmp_buf timeoutbuf; 599ea022d16SRodney W. Grimes 600ea022d16SRodney W. Grimes void 601dc4c3024SWarner Losh timer(int sig __unused) 602ea022d16SRodney W. Grimes { 603c9374115SDavid E. O'Brien if (++timeouts > MAX_TIMEOUTS) 604ea022d16SRodney W. Grimes exit(1); 605ea022d16SRodney W. Grimes longjmp(timeoutbuf, 1); 606ea022d16SRodney W. Grimes } 607ea022d16SRodney W. Grimes 608ea022d16SRodney W. Grimes /* 609ea022d16SRodney W. Grimes * Send the requested file. 610ea022d16SRodney W. Grimes */ 611ea022d16SRodney W. Grimes void 612dc4c3024SWarner Losh xmitfile(struct formats *pf) 613ea022d16SRodney W. Grimes { 614f49c0dc0SDavid Malone struct tftphdr *dp; 615dc4c3024SWarner Losh struct tftphdr *ap; /* ack packet */ 616dc4c3024SWarner Losh int size, n; 61767034ac6SJeroen Ruigrok van der Werven volatile unsigned short block; 618ea022d16SRodney W. Grimes 619ea022d16SRodney W. Grimes signal(SIGALRM, timer); 620ea022d16SRodney W. Grimes dp = r_init(); 621ea022d16SRodney W. Grimes ap = (struct tftphdr *)ackbuf; 622ea022d16SRodney W. Grimes block = 1; 623ea022d16SRodney W. Grimes do { 624ea022d16SRodney W. Grimes size = readit(file, &dp, pf->f_convert); 625ea022d16SRodney W. Grimes if (size < 0) { 626ea022d16SRodney W. Grimes nak(errno + 100); 627ea022d16SRodney W. Grimes goto abort; 628ea022d16SRodney W. Grimes } 629ea022d16SRodney W. Grimes dp->th_opcode = htons((u_short)DATA); 630ea022d16SRodney W. Grimes dp->th_block = htons((u_short)block); 631c9374115SDavid E. O'Brien timeouts = 0; 632ea022d16SRodney W. Grimes (void)setjmp(timeoutbuf); 633ea022d16SRodney W. Grimes 634ea022d16SRodney W. Grimes send_data: 635ff93f08cSDoug Ambrisko { 636ff93f08cSDoug Ambrisko int i, t = 1; 637ff93f08cSDoug Ambrisko for (i = 0; ; i++){ 638ea022d16SRodney W. Grimes if (send(peer, dp, size + 4, 0) != size + 4) { 639ff93f08cSDoug Ambrisko sleep(t); 640ff93f08cSDoug Ambrisko t = (t < 32) ? t<< 1 : t; 641ff93f08cSDoug Ambrisko if (i >= 12) { 642a8faeabcSPhilippe Charnier syslog(LOG_ERR, "write: %m"); 643ea022d16SRodney W. Grimes goto abort; 644ea022d16SRodney W. Grimes } 645ff93f08cSDoug Ambrisko } 646ff93f08cSDoug Ambrisko break; 647ff93f08cSDoug Ambrisko } 648ff93f08cSDoug Ambrisko } 649ea022d16SRodney W. Grimes read_ahead(file, pf->f_convert); 650ea022d16SRodney W. Grimes for ( ; ; ) { 651ea022d16SRodney W. Grimes alarm(rexmtval); /* read the ack */ 652ea022d16SRodney W. Grimes n = recv(peer, ackbuf, sizeof (ackbuf), 0); 653ea022d16SRodney W. Grimes alarm(0); 654ea022d16SRodney W. Grimes if (n < 0) { 655a8faeabcSPhilippe Charnier syslog(LOG_ERR, "read: %m"); 656ea022d16SRodney W. Grimes goto abort; 657ea022d16SRodney W. Grimes } 658ea022d16SRodney W. Grimes ap->th_opcode = ntohs((u_short)ap->th_opcode); 659ea022d16SRodney W. Grimes ap->th_block = ntohs((u_short)ap->th_block); 660ea022d16SRodney W. Grimes 661ea022d16SRodney W. Grimes if (ap->th_opcode == ERROR) 662ea022d16SRodney W. Grimes goto abort; 663ea022d16SRodney W. Grimes 664ea022d16SRodney W. Grimes if (ap->th_opcode == ACK) { 665ea022d16SRodney W. Grimes if (ap->th_block == block) 666ea022d16SRodney W. Grimes break; 667ea022d16SRodney W. Grimes /* Re-synchronize with the other side */ 668ea022d16SRodney W. Grimes (void) synchnet(peer); 669ea022d16SRodney W. Grimes if (ap->th_block == (block -1)) 670ea022d16SRodney W. Grimes goto send_data; 671ea022d16SRodney W. Grimes } 672ea022d16SRodney W. Grimes 673ea022d16SRodney W. Grimes } 674ea022d16SRodney W. Grimes block++; 675ea022d16SRodney W. Grimes } while (size == SEGSIZE); 676ea022d16SRodney W. Grimes abort: 677ea022d16SRodney W. Grimes (void) fclose(file); 678ea022d16SRodney W. Grimes } 679ea022d16SRodney W. Grimes 680ea022d16SRodney W. Grimes void 681dc4c3024SWarner Losh justquit(int sig __unused) 682ea022d16SRodney W. Grimes { 683ea022d16SRodney W. Grimes exit(0); 684ea022d16SRodney W. Grimes } 685ea022d16SRodney W. Grimes 686ea022d16SRodney W. Grimes 687ea022d16SRodney W. Grimes /* 688ea022d16SRodney W. Grimes * Receive a file. 689ea022d16SRodney W. Grimes */ 690ea022d16SRodney W. Grimes void 691dc4c3024SWarner Losh recvfile(struct formats *pf) 692ea022d16SRodney W. Grimes { 693f49c0dc0SDavid Malone struct tftphdr *dp; 694dc4c3024SWarner Losh struct tftphdr *ap; /* ack buffer */ 695dc4c3024SWarner Losh int n, size; 69667034ac6SJeroen Ruigrok van der Werven volatile unsigned short block; 697ea022d16SRodney W. Grimes 698ea022d16SRodney W. Grimes signal(SIGALRM, timer); 699ea022d16SRodney W. Grimes dp = w_init(); 700ea022d16SRodney W. Grimes ap = (struct tftphdr *)ackbuf; 701ea022d16SRodney W. Grimes block = 0; 702ea022d16SRodney W. Grimes do { 703c9374115SDavid E. O'Brien timeouts = 0; 704ea022d16SRodney W. Grimes ap->th_opcode = htons((u_short)ACK); 705ea022d16SRodney W. Grimes ap->th_block = htons((u_short)block); 706ea022d16SRodney W. Grimes block++; 707ea022d16SRodney W. Grimes (void) setjmp(timeoutbuf); 708ea022d16SRodney W. Grimes send_ack: 709ea022d16SRodney W. Grimes if (send(peer, ackbuf, 4, 0) != 4) { 710a8faeabcSPhilippe Charnier syslog(LOG_ERR, "write: %m"); 711ea022d16SRodney W. Grimes goto abort; 712ea022d16SRodney W. Grimes } 713ea022d16SRodney W. Grimes write_behind(file, pf->f_convert); 714ea022d16SRodney W. Grimes for ( ; ; ) { 715ea022d16SRodney W. Grimes alarm(rexmtval); 716ea022d16SRodney W. Grimes n = recv(peer, dp, PKTSIZE, 0); 717ea022d16SRodney W. Grimes alarm(0); 718ea022d16SRodney W. Grimes if (n < 0) { /* really? */ 719a8faeabcSPhilippe Charnier syslog(LOG_ERR, "read: %m"); 720ea022d16SRodney W. Grimes goto abort; 721ea022d16SRodney W. Grimes } 722ea022d16SRodney W. Grimes dp->th_opcode = ntohs((u_short)dp->th_opcode); 723ea022d16SRodney W. Grimes dp->th_block = ntohs((u_short)dp->th_block); 724ea022d16SRodney W. Grimes if (dp->th_opcode == ERROR) 725ea022d16SRodney W. Grimes goto abort; 726ea022d16SRodney W. Grimes if (dp->th_opcode == DATA) { 727ea022d16SRodney W. Grimes if (dp->th_block == block) { 728ea022d16SRodney W. Grimes break; /* normal */ 729ea022d16SRodney W. Grimes } 730ea022d16SRodney W. Grimes /* Re-synchronize with the other side */ 731ea022d16SRodney W. Grimes (void) synchnet(peer); 732ea022d16SRodney W. Grimes if (dp->th_block == (block-1)) 733ea022d16SRodney W. Grimes goto send_ack; /* rexmit */ 734ea022d16SRodney W. Grimes } 735ea022d16SRodney W. Grimes } 736ea022d16SRodney W. Grimes /* size = write(file, dp->th_data, n - 4); */ 737ea022d16SRodney W. Grimes size = writeit(file, &dp, n - 4, pf->f_convert); 738ea022d16SRodney W. Grimes if (size != (n-4)) { /* ahem */ 739ea022d16SRodney W. Grimes if (size < 0) nak(errno + 100); 740ea022d16SRodney W. Grimes else nak(ENOSPACE); 741ea022d16SRodney W. Grimes goto abort; 742ea022d16SRodney W. Grimes } 743ea022d16SRodney W. Grimes } while (size == SEGSIZE); 744ea022d16SRodney W. Grimes write_behind(file, pf->f_convert); 745ea022d16SRodney W. Grimes (void) fclose(file); /* close data file */ 746ea022d16SRodney W. Grimes 747ea022d16SRodney W. Grimes ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 748ea022d16SRodney W. Grimes ap->th_block = htons((u_short)(block)); 749ea022d16SRodney W. Grimes (void) send(peer, ackbuf, 4, 0); 750ea022d16SRodney W. Grimes 751ea022d16SRodney W. Grimes signal(SIGALRM, justquit); /* just quit on timeout */ 752ea022d16SRodney W. Grimes alarm(rexmtval); 753ea022d16SRodney W. Grimes n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 754ea022d16SRodney W. Grimes alarm(0); 755ea022d16SRodney W. Grimes if (n >= 4 && /* if read some data */ 756ea022d16SRodney W. Grimes dp->th_opcode == DATA && /* and got a data block */ 757ea022d16SRodney W. Grimes block == dp->th_block) { /* then my last ack was lost */ 758ea022d16SRodney W. Grimes (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 759ea022d16SRodney W. Grimes } 760ea022d16SRodney W. Grimes abort: 761ea022d16SRodney W. Grimes return; 762ea022d16SRodney W. Grimes } 763ea022d16SRodney W. Grimes 764ea022d16SRodney W. Grimes struct errmsg { 765ea022d16SRodney W. Grimes int e_code; 766f49c0dc0SDavid Malone const char *e_msg; 767ea022d16SRodney W. Grimes } errmsgs[] = { 768ea022d16SRodney W. Grimes { EUNDEF, "Undefined error code" }, 769ea022d16SRodney W. Grimes { ENOTFOUND, "File not found" }, 770ea022d16SRodney W. Grimes { EACCESS, "Access violation" }, 771ea022d16SRodney W. Grimes { ENOSPACE, "Disk full or allocation exceeded" }, 772ea022d16SRodney W. Grimes { EBADOP, "Illegal TFTP operation" }, 773ea022d16SRodney W. Grimes { EBADID, "Unknown transfer ID" }, 774ea022d16SRodney W. Grimes { EEXISTS, "File already exists" }, 775ea022d16SRodney W. Grimes { ENOUSER, "No such user" }, 776c9374115SDavid E. O'Brien { EOPTNEG, "Option negotiation" }, 777ea022d16SRodney W. Grimes { -1, 0 } 778ea022d16SRodney W. Grimes }; 779ea022d16SRodney W. Grimes 780f49c0dc0SDavid Malone static const char * 781dc4c3024SWarner Losh errtomsg(int error) 782ea022d16SRodney W. Grimes { 783f49c0dc0SDavid Malone static char ebuf[20]; 784dc4c3024SWarner Losh struct errmsg *pe; 785ea022d16SRodney W. Grimes if (error == 0) 786ea022d16SRodney W. Grimes return "success"; 787ea022d16SRodney W. Grimes for (pe = errmsgs; pe->e_code >= 0; pe++) 788ea022d16SRodney W. Grimes if (pe->e_code == error) 789ea022d16SRodney W. Grimes return pe->e_msg; 790f49c0dc0SDavid Malone snprintf(ebuf, sizeof(buf), "error %d", error); 791f49c0dc0SDavid Malone return ebuf; 792ea022d16SRodney W. Grimes } 793ea022d16SRodney W. Grimes 794ea022d16SRodney W. Grimes /* 795ea022d16SRodney W. Grimes * Send a nak packet (error message). 796ea022d16SRodney W. Grimes * Error code passed in is one of the 797ea022d16SRodney W. Grimes * standard TFTP codes, or a UNIX errno 798ea022d16SRodney W. Grimes * offset by 100. 799ea022d16SRodney W. Grimes */ 800ea022d16SRodney W. Grimes static void 801dc4c3024SWarner Losh nak(int error) 802ea022d16SRodney W. Grimes { 803dc4c3024SWarner Losh struct tftphdr *tp; 804ea022d16SRodney W. Grimes int length; 805dc4c3024SWarner Losh struct errmsg *pe; 806ea022d16SRodney W. Grimes 807ea022d16SRodney W. Grimes tp = (struct tftphdr *)buf; 808ea022d16SRodney W. Grimes tp->th_opcode = htons((u_short)ERROR); 809ea022d16SRodney W. Grimes tp->th_code = htons((u_short)error); 810ea022d16SRodney W. Grimes for (pe = errmsgs; pe->e_code >= 0; pe++) 811ea022d16SRodney W. Grimes if (pe->e_code == error) 812ea022d16SRodney W. Grimes break; 813ea022d16SRodney W. Grimes if (pe->e_code < 0) { 814ea022d16SRodney W. Grimes pe->e_msg = strerror(error - 100); 815ea022d16SRodney W. Grimes tp->th_code = EUNDEF; /* set 'undef' errorcode */ 816ea022d16SRodney W. Grimes } 817ea022d16SRodney W. Grimes strcpy(tp->th_msg, pe->e_msg); 818ea022d16SRodney W. Grimes length = strlen(pe->e_msg); 819ea022d16SRodney W. Grimes tp->th_msg[length] = '\0'; 820ea022d16SRodney W. Grimes length += 5; 821ea022d16SRodney W. Grimes if (send(peer, buf, length, 0) != length) 822a8faeabcSPhilippe Charnier syslog(LOG_ERR, "nak: %m"); 823ea022d16SRodney W. Grimes } 824c9374115SDavid E. O'Brien 8254dac6235SHajimu UMEMOTO /* translate IPv4 mapped IPv6 address to IPv4 address */ 8264dac6235SHajimu UMEMOTO static void 8274dac6235SHajimu UMEMOTO unmappedaddr(struct sockaddr_in6 *sin6) 8284dac6235SHajimu UMEMOTO { 8294dac6235SHajimu UMEMOTO struct sockaddr_in *sin4; 8304dac6235SHajimu UMEMOTO u_int32_t addr; 8314dac6235SHajimu UMEMOTO int port; 8324dac6235SHajimu UMEMOTO 8334dac6235SHajimu UMEMOTO if (sin6->sin6_family != AF_INET6 || 8344dac6235SHajimu UMEMOTO !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 8354dac6235SHajimu UMEMOTO return; 8364dac6235SHajimu UMEMOTO sin4 = (struct sockaddr_in *)sin6; 8374dac6235SHajimu UMEMOTO addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 8384dac6235SHajimu UMEMOTO port = sin6->sin6_port; 8394dac6235SHajimu UMEMOTO memset(sin4, 0, sizeof(struct sockaddr_in)); 8404dac6235SHajimu UMEMOTO sin4->sin_addr.s_addr = addr; 8414dac6235SHajimu UMEMOTO sin4->sin_port = port; 8424dac6235SHajimu UMEMOTO sin4->sin_family = AF_INET; 8434dac6235SHajimu UMEMOTO sin4->sin_len = sizeof(struct sockaddr_in); 8444dac6235SHajimu UMEMOTO } 8454dac6235SHajimu UMEMOTO 846c9374115SDavid E. O'Brien /* 847c9374115SDavid E. O'Brien * Send an oack packet (option acknowledgement). 848c9374115SDavid E. O'Brien */ 849c9374115SDavid E. O'Brien static void 850dc4c3024SWarner Losh oack(void) 851c9374115SDavid E. O'Brien { 852c9374115SDavid E. O'Brien struct tftphdr *tp, *ap; 853c9374115SDavid E. O'Brien int size, i, n; 854c9374115SDavid E. O'Brien char *bp; 855c9374115SDavid E. O'Brien 856c9374115SDavid E. O'Brien tp = (struct tftphdr *)buf; 857c9374115SDavid E. O'Brien bp = buf + 2; 858c9374115SDavid E. O'Brien size = sizeof(buf) - 2; 859c9374115SDavid E. O'Brien tp->th_opcode = htons((u_short)OACK); 860c9374115SDavid E. O'Brien for (i = 0; options[i].o_type != NULL; i++) { 861c9374115SDavid E. O'Brien if (options[i].o_request) { 862c9374115SDavid E. O'Brien n = snprintf(bp, size, "%s%c%d", options[i].o_type, 863c9374115SDavid E. O'Brien 0, options[i].o_reply); 864c9374115SDavid E. O'Brien bp += n+1; 865c9374115SDavid E. O'Brien size -= n+1; 866c9374115SDavid E. O'Brien if (size < 0) { 867c9374115SDavid E. O'Brien syslog(LOG_ERR, "oack: buffer overflow"); 868c9374115SDavid E. O'Brien exit(1); 869c9374115SDavid E. O'Brien } 870c9374115SDavid E. O'Brien } 871c9374115SDavid E. O'Brien } 872c9374115SDavid E. O'Brien size = bp - buf; 873c9374115SDavid E. O'Brien ap = (struct tftphdr *)ackbuf; 874c9374115SDavid E. O'Brien signal(SIGALRM, timer); 875c9374115SDavid E. O'Brien timeouts = 0; 876c9374115SDavid E. O'Brien 877c9374115SDavid E. O'Brien (void)setjmp(timeoutbuf); 878c9374115SDavid E. O'Brien if (send(peer, buf, size, 0) != size) { 879c9374115SDavid E. O'Brien syslog(LOG_INFO, "oack: %m"); 880c9374115SDavid E. O'Brien exit(1); 881c9374115SDavid E. O'Brien } 882c9374115SDavid E. O'Brien 883c9374115SDavid E. O'Brien for (;;) { 884c9374115SDavid E. O'Brien alarm(rexmtval); 885c9374115SDavid E. O'Brien n = recv(peer, ackbuf, sizeof (ackbuf), 0); 886c9374115SDavid E. O'Brien alarm(0); 887c9374115SDavid E. O'Brien if (n < 0) { 888c9374115SDavid E. O'Brien syslog(LOG_ERR, "recv: %m"); 889c9374115SDavid E. O'Brien exit(1); 890c9374115SDavid E. O'Brien } 891c9374115SDavid E. O'Brien ap->th_opcode = ntohs((u_short)ap->th_opcode); 892c9374115SDavid E. O'Brien ap->th_block = ntohs((u_short)ap->th_block); 893c9374115SDavid E. O'Brien if (ap->th_opcode == ERROR) 894c9374115SDavid E. O'Brien exit(1); 895c9374115SDavid E. O'Brien if (ap->th_opcode == ACK && ap->th_block == 0) 896c9374115SDavid E. O'Brien break; 897c9374115SDavid E. O'Brien } 898c9374115SDavid E. O'Brien } 899