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. 32148531efSWolfram Schneider * 33fca08b7cSWarner Losh * $Id: tftpd.c,v 1.7 1997/02/22 14:22:36 peter Exp $ 34ea022d16SRodney W. Grimes */ 35ea022d16SRodney W. Grimes 36ea022d16SRodney W. Grimes #ifndef lint 37ea022d16SRodney W. Grimes static char copyright[] = 38ea022d16SRodney W. Grimes "@(#) Copyright (c) 1983, 1993\n\ 39ea022d16SRodney W. Grimes The Regents of the University of California. All rights reserved.\n"; 40ea022d16SRodney W. Grimes #endif /* not lint */ 41ea022d16SRodney W. Grimes 42ea022d16SRodney W. Grimes #ifndef lint 43ea022d16SRodney W. Grimes static char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93"; 44ea022d16SRodney W. Grimes #endif /* not lint */ 45ea022d16SRodney W. Grimes 46ea022d16SRodney W. Grimes /* 47ea022d16SRodney W. Grimes * Trivial file transfer protocol server. 48ea022d16SRodney W. Grimes * 49ea022d16SRodney W. Grimes * This version includes many modifications by Jim Guyton 50ea022d16SRodney W. Grimes * <guyton@rand-unix>. 51ea022d16SRodney W. Grimes */ 52ea022d16SRodney W. Grimes 53ea022d16SRodney W. Grimes #include <sys/param.h> 54ea022d16SRodney W. Grimes #include <sys/ioctl.h> 55ea022d16SRodney W. Grimes #include <sys/stat.h> 56ea022d16SRodney W. Grimes #include <sys/socket.h> 578ea31785SWarner Losh #include <sys/types.h> 58ea022d16SRodney W. Grimes 59ea022d16SRodney W. Grimes #include <netinet/in.h> 60ea022d16SRodney W. Grimes #include <arpa/tftp.h> 61ea022d16SRodney W. Grimes #include <arpa/inet.h> 62ea022d16SRodney W. Grimes 63ea022d16SRodney W. Grimes #include <ctype.h> 64ea022d16SRodney W. Grimes #include <errno.h> 65ea022d16SRodney W. Grimes #include <fcntl.h> 66ea022d16SRodney W. Grimes #include <netdb.h> 67ea022d16SRodney W. Grimes #include <setjmp.h> 68ea022d16SRodney W. Grimes #include <signal.h> 69ea022d16SRodney W. Grimes #include <stdio.h> 70ea022d16SRodney W. Grimes #include <stdlib.h> 71ea022d16SRodney W. Grimes #include <string.h> 72ea022d16SRodney W. Grimes #include <syslog.h> 73ea022d16SRodney W. Grimes #include <unistd.h> 748ea31785SWarner Losh #include <pwd.h> 75ea022d16SRodney W. Grimes 76ea022d16SRodney W. Grimes #include "tftpsubs.h" 77ea022d16SRodney W. Grimes 78ea022d16SRodney W. Grimes #define TIMEOUT 5 79ea022d16SRodney W. Grimes 80ea022d16SRodney W. Grimes int peer; 81ea022d16SRodney W. Grimes int rexmtval = TIMEOUT; 82ea022d16SRodney W. Grimes int maxtimeout = 5*TIMEOUT; 83ea022d16SRodney W. Grimes 84ea022d16SRodney W. Grimes #define PKTSIZE SEGSIZE+4 85ea022d16SRodney W. Grimes char buf[PKTSIZE]; 86ea022d16SRodney W. Grimes char ackbuf[PKTSIZE]; 87ea022d16SRodney W. Grimes struct sockaddr_in from; 88ea022d16SRodney W. Grimes int fromlen; 89ea022d16SRodney W. Grimes 90ea022d16SRodney W. Grimes void tftp __P((struct tftphdr *, int)); 91ea022d16SRodney W. Grimes 92ea022d16SRodney W. Grimes /* 93ea022d16SRodney W. Grimes * Null-terminated directory prefix list for absolute pathname requests and 94ea022d16SRodney W. Grimes * search list for relative pathname requests. 95ea022d16SRodney W. Grimes * 96ea022d16SRodney W. Grimes * MAXDIRS should be at least as large as the number of arguments that 97ea022d16SRodney W. Grimes * inetd allows (currently 20). 98ea022d16SRodney W. Grimes */ 99ea022d16SRodney W. Grimes #define MAXDIRS 20 100ea022d16SRodney W. Grimes static struct dirlist { 101ea022d16SRodney W. Grimes char *name; 102ea022d16SRodney W. Grimes int len; 103ea022d16SRodney W. Grimes } dirs[MAXDIRS+1]; 104ea022d16SRodney W. Grimes static int suppress_naks; 105ea022d16SRodney W. Grimes static int logging; 106ea022d16SRodney W. Grimes 107ea022d16SRodney W. Grimes static char *errtomsg __P((int)); 108ea022d16SRodney W. Grimes static void nak __P((int)); 109ea022d16SRodney W. Grimes static char *verifyhost __P((struct sockaddr_in *)); 110ea022d16SRodney W. Grimes 111ea022d16SRodney W. Grimes int 112ea022d16SRodney W. Grimes main(argc, argv) 113ea022d16SRodney W. Grimes int argc; 114ea022d16SRodney W. Grimes char *argv[]; 115ea022d16SRodney W. Grimes { 116ea022d16SRodney W. Grimes register struct tftphdr *tp; 117ea022d16SRodney W. Grimes register int n; 118ea022d16SRodney W. Grimes int ch, on; 119ea022d16SRodney W. Grimes struct sockaddr_in sin; 1208ea31785SWarner Losh char *chroot_dir = NULL; 1218ea31785SWarner Losh struct passwd *nobody; 122ea022d16SRodney W. Grimes 123ea022d16SRodney W. Grimes openlog("tftpd", LOG_PID, LOG_FTP); 1248ea31785SWarner Losh while ((ch = getopt(argc, argv, "lns:")) != EOF) { 125ea022d16SRodney W. Grimes switch (ch) { 126ea022d16SRodney W. Grimes case 'l': 127ea022d16SRodney W. Grimes logging = 1; 128ea022d16SRodney W. Grimes break; 129ea022d16SRodney W. Grimes case 'n': 130ea022d16SRodney W. Grimes suppress_naks = 1; 131ea022d16SRodney W. Grimes break; 1328ea31785SWarner Losh case 's': 1338ea31785SWarner Losh chroot_dir = optarg; 1348ea31785SWarner Losh break; 135ea022d16SRodney W. Grimes default: 136ea022d16SRodney W. Grimes syslog(LOG_WARNING, "ignoring unknown option -%c", ch); 137ea022d16SRodney W. Grimes } 138ea022d16SRodney W. Grimes } 139ea022d16SRodney W. Grimes if (optind < argc) { 140ea022d16SRodney W. Grimes struct dirlist *dirp; 141ea022d16SRodney W. Grimes 142ea022d16SRodney W. Grimes /* Get list of directory prefixes. Skip relative pathnames. */ 143ea022d16SRodney W. Grimes for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; 144ea022d16SRodney W. Grimes optind++) { 145ea022d16SRodney W. Grimes if (argv[optind][0] == '/') { 146ea022d16SRodney W. Grimes dirp->name = argv[optind]; 147ea022d16SRodney W. Grimes dirp->len = strlen(dirp->name); 148ea022d16SRodney W. Grimes dirp++; 149ea022d16SRodney W. Grimes } 150ea022d16SRodney W. Grimes } 151ea022d16SRodney W. Grimes } 1528ea31785SWarner Losh else if (chroot_dir) { 1538ea31785SWarner Losh dirs->name = "/"; 1548ea31785SWarner Losh dirs->len = 1; 1558ea31785SWarner Losh } 156ea022d16SRodney W. Grimes 157ea022d16SRodney W. Grimes on = 1; 158ea022d16SRodney W. Grimes if (ioctl(0, FIONBIO, &on) < 0) { 159ea022d16SRodney W. Grimes syslog(LOG_ERR, "ioctl(FIONBIO): %m\n"); 160ea022d16SRodney W. Grimes exit(1); 161ea022d16SRodney W. Grimes } 162ea022d16SRodney W. Grimes fromlen = sizeof (from); 163ea022d16SRodney W. Grimes n = recvfrom(0, buf, sizeof (buf), 0, 164ea022d16SRodney W. Grimes (struct sockaddr *)&from, &fromlen); 165ea022d16SRodney W. Grimes if (n < 0) { 166ea022d16SRodney W. Grimes syslog(LOG_ERR, "recvfrom: %m\n"); 167ea022d16SRodney W. Grimes exit(1); 168ea022d16SRodney W. Grimes } 169ea022d16SRodney W. Grimes /* 170ea022d16SRodney W. Grimes * Now that we have read the message out of the UDP 171ea022d16SRodney W. Grimes * socket, we fork and exit. Thus, inetd will go back 172ea022d16SRodney W. Grimes * to listening to the tftp port, and the next request 173ea022d16SRodney W. Grimes * to come in will start up a new instance of tftpd. 174ea022d16SRodney W. Grimes * 175ea022d16SRodney W. Grimes * We do this so that inetd can run tftpd in "wait" mode. 176ea022d16SRodney W. Grimes * The problem with tftpd running in "nowait" mode is that 177ea022d16SRodney W. Grimes * inetd may get one or more successful "selects" on the 178ea022d16SRodney W. Grimes * tftp port before we do our receive, so more than one 179ea022d16SRodney W. Grimes * instance of tftpd may be started up. Worse, if tftpd 180ea022d16SRodney W. Grimes * break before doing the above "recvfrom", inetd would 181ea022d16SRodney W. Grimes * spawn endless instances, clogging the system. 182ea022d16SRodney W. Grimes */ 183ea022d16SRodney W. Grimes { 184ea022d16SRodney W. Grimes int pid; 185ea022d16SRodney W. Grimes int i, j; 186ea022d16SRodney W. Grimes 187ea022d16SRodney W. Grimes for (i = 1; i < 20; i++) { 188ea022d16SRodney W. Grimes pid = fork(); 189ea022d16SRodney W. Grimes if (pid < 0) { 190ea022d16SRodney W. Grimes sleep(i); 191ea022d16SRodney W. Grimes /* 192ea022d16SRodney W. Grimes * flush out to most recently sent request. 193ea022d16SRodney W. Grimes * 194ea022d16SRodney W. Grimes * This may drop some request, but those 195ea022d16SRodney W. Grimes * will be resent by the clients when 196ea022d16SRodney W. Grimes * they timeout. The positive effect of 197ea022d16SRodney W. Grimes * this flush is to (try to) prevent more 198ea022d16SRodney W. Grimes * than one tftpd being started up to service 199ea022d16SRodney W. Grimes * a single request from a single client. 200ea022d16SRodney W. Grimes */ 201ea022d16SRodney W. Grimes j = sizeof from; 202ea022d16SRodney W. Grimes i = recvfrom(0, buf, sizeof (buf), 0, 203ea022d16SRodney W. Grimes (struct sockaddr *)&from, &j); 204ea022d16SRodney W. Grimes if (i > 0) { 205ea022d16SRodney W. Grimes n = i; 206ea022d16SRodney W. Grimes fromlen = j; 207ea022d16SRodney W. Grimes } 208ea022d16SRodney W. Grimes } else { 209ea022d16SRodney W. Grimes break; 210ea022d16SRodney W. Grimes } 211ea022d16SRodney W. Grimes } 212ea022d16SRodney W. Grimes if (pid < 0) { 213ea022d16SRodney W. Grimes syslog(LOG_ERR, "fork: %m\n"); 214ea022d16SRodney W. Grimes exit(1); 215ea022d16SRodney W. Grimes } else if (pid != 0) { 216ea022d16SRodney W. Grimes exit(0); 217ea022d16SRodney W. Grimes } 218ea022d16SRodney W. Grimes } 2198ea31785SWarner Losh 2208ea31785SWarner Losh /* 2218ea31785SWarner Losh * Since we exit here, we should do that only after the above 2228ea31785SWarner Losh * recvfrom to keep inetd from constantly forking should there 2238ea31785SWarner Losh * be a problem. See the above comment about system clogging. 2248ea31785SWarner Losh */ 2258ea31785SWarner Losh if (chroot_dir) { 2268ea31785SWarner Losh /* Must get this before chroot because /etc might go away */ 2278ea31785SWarner Losh if ((nobody = getpwnam("nobody")) == NULL) { 2288ea31785SWarner Losh syslog(LOG_ERR, "nobody: no such user"); 2298ea31785SWarner Losh exit(1); 2308ea31785SWarner Losh } 2318ea31785SWarner Losh if (chroot(chroot_dir)) { 2328ea31785SWarner Losh syslog(LOG_ERR, "chroot: %s: %m", chroot_dir); 2338ea31785SWarner Losh exit(1); 2348ea31785SWarner Losh } 2358ea31785SWarner Losh chdir( "/" ); 2368ea31785SWarner Losh setuid(nobody->pw_uid); 2378ea31785SWarner Losh } 2388ea31785SWarner Losh 239ea022d16SRodney W. Grimes from.sin_family = AF_INET; 240ea022d16SRodney W. Grimes alarm(0); 241ea022d16SRodney W. Grimes close(0); 242ea022d16SRodney W. Grimes close(1); 243ea022d16SRodney W. Grimes peer = socket(AF_INET, SOCK_DGRAM, 0); 244ea022d16SRodney W. Grimes if (peer < 0) { 245ea022d16SRodney W. Grimes syslog(LOG_ERR, "socket: %m\n"); 246ea022d16SRodney W. Grimes exit(1); 247ea022d16SRodney W. Grimes } 248ea022d16SRodney W. Grimes memset(&sin, 0, sizeof(sin)); 249ea022d16SRodney W. Grimes sin.sin_family = AF_INET; 250ea022d16SRodney W. Grimes if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) { 251ea022d16SRodney W. Grimes syslog(LOG_ERR, "bind: %m\n"); 252ea022d16SRodney W. Grimes exit(1); 253ea022d16SRodney W. Grimes } 254ea022d16SRodney W. Grimes if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) { 255ea022d16SRodney W. Grimes syslog(LOG_ERR, "connect: %m\n"); 256ea022d16SRodney W. Grimes exit(1); 257ea022d16SRodney W. Grimes } 258ea022d16SRodney W. Grimes tp = (struct tftphdr *)buf; 259ea022d16SRodney W. Grimes tp->th_opcode = ntohs(tp->th_opcode); 260ea022d16SRodney W. Grimes if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 261ea022d16SRodney W. Grimes tftp(tp, n); 262ea022d16SRodney W. Grimes exit(1); 263ea022d16SRodney W. Grimes } 264ea022d16SRodney W. Grimes 265ea022d16SRodney W. Grimes struct formats; 266ea022d16SRodney W. Grimes int validate_access __P((char **, int)); 267ea022d16SRodney W. Grimes void sendfile __P((struct formats *)); 268ea022d16SRodney W. Grimes void recvfile __P((struct formats *)); 269ea022d16SRodney W. Grimes 270ea022d16SRodney W. Grimes struct formats { 271ea022d16SRodney W. Grimes char *f_mode; 272ea022d16SRodney W. Grimes int (*f_validate) __P((char **, int)); 273ea022d16SRodney W. Grimes void (*f_send) __P((struct formats *)); 274ea022d16SRodney W. Grimes void (*f_recv) __P((struct formats *)); 275ea022d16SRodney W. Grimes int f_convert; 276ea022d16SRodney W. Grimes } formats[] = { 277ea022d16SRodney W. Grimes { "netascii", validate_access, sendfile, recvfile, 1 }, 278ea022d16SRodney W. Grimes { "octet", validate_access, sendfile, recvfile, 0 }, 279ea022d16SRodney W. Grimes #ifdef notdef 280ea022d16SRodney W. Grimes { "mail", validate_user, sendmail, recvmail, 1 }, 281ea022d16SRodney W. Grimes #endif 282ea022d16SRodney W. Grimes { 0 } 283ea022d16SRodney W. Grimes }; 284ea022d16SRodney W. Grimes 285ea022d16SRodney W. Grimes /* 286ea022d16SRodney W. Grimes * Handle initial connection protocol. 287ea022d16SRodney W. Grimes */ 288ea022d16SRodney W. Grimes void 289ea022d16SRodney W. Grimes tftp(tp, size) 290ea022d16SRodney W. Grimes struct tftphdr *tp; 291ea022d16SRodney W. Grimes int size; 292ea022d16SRodney W. Grimes { 293ea022d16SRodney W. Grimes register char *cp; 294ea022d16SRodney W. Grimes int first = 1, ecode; 295ea022d16SRodney W. Grimes register struct formats *pf; 296ea022d16SRodney W. Grimes char *filename, *mode; 297ea022d16SRodney W. Grimes 298ea022d16SRodney W. Grimes filename = cp = tp->th_stuff; 299ea022d16SRodney W. Grimes again: 300ea022d16SRodney W. Grimes while (cp < buf + size) { 301ea022d16SRodney W. Grimes if (*cp == '\0') 302ea022d16SRodney W. Grimes break; 303ea022d16SRodney W. Grimes cp++; 304ea022d16SRodney W. Grimes } 305ea022d16SRodney W. Grimes if (*cp != '\0') { 306ea022d16SRodney W. Grimes nak(EBADOP); 307ea022d16SRodney W. Grimes exit(1); 308ea022d16SRodney W. Grimes } 309ea022d16SRodney W. Grimes if (first) { 310ea022d16SRodney W. Grimes mode = ++cp; 311ea022d16SRodney W. Grimes first = 0; 312ea022d16SRodney W. Grimes goto again; 313ea022d16SRodney W. Grimes } 314ea022d16SRodney W. Grimes for (cp = mode; *cp; cp++) 315ea022d16SRodney W. Grimes if (isupper(*cp)) 316ea022d16SRodney W. Grimes *cp = tolower(*cp); 317ea022d16SRodney W. Grimes for (pf = formats; pf->f_mode; pf++) 318ea022d16SRodney W. Grimes if (strcmp(pf->f_mode, mode) == 0) 319ea022d16SRodney W. Grimes break; 320ea022d16SRodney W. Grimes if (pf->f_mode == 0) { 321ea022d16SRodney W. Grimes nak(EBADOP); 322ea022d16SRodney W. Grimes exit(1); 323ea022d16SRodney W. Grimes } 324ea022d16SRodney W. Grimes ecode = (*pf->f_validate)(&filename, tp->th_opcode); 325ea022d16SRodney W. Grimes if (logging) { 326ea022d16SRodney W. Grimes syslog(LOG_INFO, "%s: %s request for %s: %s", 327ea022d16SRodney W. Grimes verifyhost(&from), 328ea022d16SRodney W. Grimes tp->th_opcode == WRQ ? "write" : "read", 329ea022d16SRodney W. Grimes filename, errtomsg(ecode)); 330ea022d16SRodney W. Grimes } 331ea022d16SRodney W. Grimes if (ecode) { 332ea022d16SRodney W. Grimes /* 333ea022d16SRodney W. Grimes * Avoid storms of naks to a RRQ broadcast for a relative 334ea022d16SRodney W. Grimes * bootfile pathname from a diskless Sun. 335ea022d16SRodney W. Grimes */ 336ea022d16SRodney W. Grimes if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) 337ea022d16SRodney W. Grimes exit(0); 338ea022d16SRodney W. Grimes nak(ecode); 339ea022d16SRodney W. Grimes exit(1); 340ea022d16SRodney W. Grimes } 341ea022d16SRodney W. Grimes if (tp->th_opcode == WRQ) 342ea022d16SRodney W. Grimes (*pf->f_recv)(pf); 343ea022d16SRodney W. Grimes else 344ea022d16SRodney W. Grimes (*pf->f_send)(pf); 345ea022d16SRodney W. Grimes exit(0); 346ea022d16SRodney W. Grimes } 347ea022d16SRodney W. Grimes 348ea022d16SRodney W. Grimes 349ea022d16SRodney W. Grimes FILE *file; 350ea022d16SRodney W. Grimes 351ea022d16SRodney W. Grimes /* 352ea022d16SRodney W. Grimes * Validate file access. Since we 353ea022d16SRodney W. Grimes * have no uid or gid, for now require 354ea022d16SRodney W. Grimes * file to exist and be publicly 355ea022d16SRodney W. Grimes * readable/writable. 356ea022d16SRodney W. Grimes * If we were invoked with arguments 357ea022d16SRodney W. Grimes * from inetd then the file must also be 358ea022d16SRodney W. Grimes * in one of the given directory prefixes. 359ea022d16SRodney W. Grimes * Note also, full path name must be 360ea022d16SRodney W. Grimes * given as we have no login directory. 361ea022d16SRodney W. Grimes */ 362ea022d16SRodney W. Grimes int 363ea022d16SRodney W. Grimes validate_access(filep, mode) 364ea022d16SRodney W. Grimes char **filep; 365ea022d16SRodney W. Grimes int mode; 366ea022d16SRodney W. Grimes { 367ea022d16SRodney W. Grimes struct stat stbuf; 368ea022d16SRodney W. Grimes int fd; 369ea022d16SRodney W. Grimes struct dirlist *dirp; 370ea022d16SRodney W. Grimes static char pathname[MAXPATHLEN]; 371ea022d16SRodney W. Grimes char *filename = *filep; 372ea022d16SRodney W. Grimes 373ea022d16SRodney W. Grimes /* 374ea022d16SRodney W. Grimes * Prevent tricksters from getting around the directory restrictions 375ea022d16SRodney W. Grimes */ 376ea022d16SRodney W. Grimes if (strstr(filename, "/../")) 377ea022d16SRodney W. Grimes return (EACCESS); 378ea022d16SRodney W. Grimes 379ea022d16SRodney W. Grimes if (*filename == '/') { 380ea022d16SRodney W. Grimes /* 381ea022d16SRodney W. Grimes * Allow the request if it's in one of the approved locations. 382ea022d16SRodney W. Grimes * Special case: check the null prefix ("/") by looking 383ea022d16SRodney W. Grimes * for length = 1 and relying on the arg. processing that 384ea022d16SRodney W. Grimes * it's a /. 385ea022d16SRodney W. Grimes */ 386ea022d16SRodney W. Grimes for (dirp = dirs; dirp->name != NULL; dirp++) { 387ea022d16SRodney W. Grimes if (dirp->len == 1 || 388ea022d16SRodney W. Grimes (!strncmp(filename, dirp->name, dirp->len) && 389ea022d16SRodney W. Grimes filename[dirp->len] == '/')) 390ea022d16SRodney W. Grimes break; 391ea022d16SRodney W. Grimes } 392ea022d16SRodney W. Grimes /* If directory list is empty, allow access to any file */ 393ea022d16SRodney W. Grimes if (dirp->name == NULL && dirp != dirs) 394ea022d16SRodney W. Grimes return (EACCESS); 395ea022d16SRodney W. Grimes if (stat(filename, &stbuf) < 0) 396ea022d16SRodney W. Grimes return (errno == ENOENT ? ENOTFOUND : EACCESS); 397ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IFMT) != S_IFREG) 398ea022d16SRodney W. Grimes return (ENOTFOUND); 399ea022d16SRodney W. Grimes if (mode == RRQ) { 400ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IROTH) == 0) 401ea022d16SRodney W. Grimes return (EACCESS); 402ea022d16SRodney W. Grimes } else { 403ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IWOTH) == 0) 404ea022d16SRodney W. Grimes return (EACCESS); 405ea022d16SRodney W. Grimes } 406ea022d16SRodney W. Grimes } else { 407ea022d16SRodney W. Grimes int err; 408ea022d16SRodney W. Grimes 409ea022d16SRodney W. Grimes /* 410ea022d16SRodney W. Grimes * Relative file name: search the approved locations for it. 41123adc6b8SJordan K. Hubbard * Don't allow write requests that avoid directory 412ea022d16SRodney W. Grimes * restrictions. 413ea022d16SRodney W. Grimes */ 414ea022d16SRodney W. Grimes 41523adc6b8SJordan K. Hubbard if (!strncmp(filename, "../", 3)) 416ea022d16SRodney W. Grimes return (EACCESS); 417ea022d16SRodney W. Grimes 418ea022d16SRodney W. Grimes /* 419ea022d16SRodney W. Grimes * If the file exists in one of the directories and isn't 420ea022d16SRodney W. Grimes * readable, continue looking. However, change the error code 421ea022d16SRodney W. Grimes * to give an indication that the file exists. 422ea022d16SRodney W. Grimes */ 423ea022d16SRodney W. Grimes err = ENOTFOUND; 424ea022d16SRodney W. Grimes for (dirp = dirs; dirp->name != NULL; dirp++) { 425fca08b7cSWarner Losh snprintf(pathname, sizeof(pathname), "%s/%s", 426fca08b7cSWarner Losh dirp->name, filename); 427ea022d16SRodney W. Grimes if (stat(pathname, &stbuf) == 0 && 428ea022d16SRodney W. Grimes (stbuf.st_mode & S_IFMT) == S_IFREG) { 429ea022d16SRodney W. Grimes if ((stbuf.st_mode & S_IROTH) != 0) { 430ea022d16SRodney W. Grimes break; 431ea022d16SRodney W. Grimes } 432ea022d16SRodney W. Grimes err = EACCESS; 433ea022d16SRodney W. Grimes } 434ea022d16SRodney W. Grimes } 435ea022d16SRodney W. Grimes if (dirp->name == NULL) 436ea022d16SRodney W. Grimes return (err); 437ea022d16SRodney W. Grimes *filep = filename = pathname; 438ea022d16SRodney W. Grimes } 43984e1b7d2SJoerg Wunsch fd = open(filename, mode == RRQ ? O_RDONLY : O_WRONLY|O_TRUNC); 440ea022d16SRodney W. Grimes if (fd < 0) 441ea022d16SRodney W. Grimes return (errno + 100); 442ea022d16SRodney W. Grimes file = fdopen(fd, (mode == RRQ)? "r":"w"); 443ea022d16SRodney W. Grimes if (file == NULL) { 444ea022d16SRodney W. Grimes return errno+100; 445ea022d16SRodney W. Grimes } 446ea022d16SRodney W. Grimes return (0); 447ea022d16SRodney W. Grimes } 448ea022d16SRodney W. Grimes 449ea022d16SRodney W. Grimes int timeout; 450ea022d16SRodney W. Grimes jmp_buf timeoutbuf; 451ea022d16SRodney W. Grimes 452ea022d16SRodney W. Grimes void 453ea022d16SRodney W. Grimes timer() 454ea022d16SRodney W. Grimes { 455ea022d16SRodney W. Grimes 456ea022d16SRodney W. Grimes timeout += rexmtval; 457ea022d16SRodney W. Grimes if (timeout >= maxtimeout) 458ea022d16SRodney W. Grimes exit(1); 459ea022d16SRodney W. Grimes longjmp(timeoutbuf, 1); 460ea022d16SRodney W. Grimes } 461ea022d16SRodney W. Grimes 462ea022d16SRodney W. Grimes /* 463ea022d16SRodney W. Grimes * Send the requested file. 464ea022d16SRodney W. Grimes */ 465ea022d16SRodney W. Grimes void 466ea022d16SRodney W. Grimes sendfile(pf) 467ea022d16SRodney W. Grimes struct formats *pf; 468ea022d16SRodney W. Grimes { 469ea022d16SRodney W. Grimes struct tftphdr *dp, *r_init(); 470ea022d16SRodney W. Grimes register struct tftphdr *ap; /* ack packet */ 471ea022d16SRodney W. Grimes register int size, n; 472ea022d16SRodney W. Grimes volatile int block; 473ea022d16SRodney W. Grimes 474ea022d16SRodney W. Grimes signal(SIGALRM, timer); 475ea022d16SRodney W. Grimes dp = r_init(); 476ea022d16SRodney W. Grimes ap = (struct tftphdr *)ackbuf; 477ea022d16SRodney W. Grimes block = 1; 478ea022d16SRodney W. Grimes do { 479ea022d16SRodney W. Grimes size = readit(file, &dp, pf->f_convert); 480ea022d16SRodney W. Grimes if (size < 0) { 481ea022d16SRodney W. Grimes nak(errno + 100); 482ea022d16SRodney W. Grimes goto abort; 483ea022d16SRodney W. Grimes } 484ea022d16SRodney W. Grimes dp->th_opcode = htons((u_short)DATA); 485ea022d16SRodney W. Grimes dp->th_block = htons((u_short)block); 486ea022d16SRodney W. Grimes timeout = 0; 487ea022d16SRodney W. Grimes (void)setjmp(timeoutbuf); 488ea022d16SRodney W. Grimes 489ea022d16SRodney W. Grimes send_data: 490ea022d16SRodney W. Grimes if (send(peer, dp, size + 4, 0) != size + 4) { 491ea022d16SRodney W. Grimes syslog(LOG_ERR, "tftpd: write: %m\n"); 492ea022d16SRodney W. Grimes goto abort; 493ea022d16SRodney W. Grimes } 494ea022d16SRodney W. Grimes read_ahead(file, pf->f_convert); 495ea022d16SRodney W. Grimes for ( ; ; ) { 496ea022d16SRodney W. Grimes alarm(rexmtval); /* read the ack */ 497ea022d16SRodney W. Grimes n = recv(peer, ackbuf, sizeof (ackbuf), 0); 498ea022d16SRodney W. Grimes alarm(0); 499ea022d16SRodney W. Grimes if (n < 0) { 500ea022d16SRodney W. Grimes syslog(LOG_ERR, "tftpd: read: %m\n"); 501ea022d16SRodney W. Grimes goto abort; 502ea022d16SRodney W. Grimes } 503ea022d16SRodney W. Grimes ap->th_opcode = ntohs((u_short)ap->th_opcode); 504ea022d16SRodney W. Grimes ap->th_block = ntohs((u_short)ap->th_block); 505ea022d16SRodney W. Grimes 506ea022d16SRodney W. Grimes if (ap->th_opcode == ERROR) 507ea022d16SRodney W. Grimes goto abort; 508ea022d16SRodney W. Grimes 509ea022d16SRodney W. Grimes if (ap->th_opcode == ACK) { 510ea022d16SRodney W. Grimes if (ap->th_block == block) 511ea022d16SRodney W. Grimes break; 512ea022d16SRodney W. Grimes /* Re-synchronize with the other side */ 513ea022d16SRodney W. Grimes (void) synchnet(peer); 514ea022d16SRodney W. Grimes if (ap->th_block == (block -1)) 515ea022d16SRodney W. Grimes goto send_data; 516ea022d16SRodney W. Grimes } 517ea022d16SRodney W. Grimes 518ea022d16SRodney W. Grimes } 519ea022d16SRodney W. Grimes block++; 520ea022d16SRodney W. Grimes } while (size == SEGSIZE); 521ea022d16SRodney W. Grimes abort: 522ea022d16SRodney W. Grimes (void) fclose(file); 523ea022d16SRodney W. Grimes } 524ea022d16SRodney W. Grimes 525ea022d16SRodney W. Grimes void 526ea022d16SRodney W. Grimes justquit() 527ea022d16SRodney W. Grimes { 528ea022d16SRodney W. Grimes exit(0); 529ea022d16SRodney W. Grimes } 530ea022d16SRodney W. Grimes 531ea022d16SRodney W. Grimes 532ea022d16SRodney W. Grimes /* 533ea022d16SRodney W. Grimes * Receive a file. 534ea022d16SRodney W. Grimes */ 535ea022d16SRodney W. Grimes void 536ea022d16SRodney W. Grimes recvfile(pf) 537ea022d16SRodney W. Grimes struct formats *pf; 538ea022d16SRodney W. Grimes { 539ea022d16SRodney W. Grimes struct tftphdr *dp, *w_init(); 540ea022d16SRodney W. Grimes register struct tftphdr *ap; /* ack buffer */ 541ea022d16SRodney W. Grimes register int n, size; 542ea022d16SRodney W. Grimes volatile int block; 543ea022d16SRodney W. Grimes 544ea022d16SRodney W. Grimes signal(SIGALRM, timer); 545ea022d16SRodney W. Grimes dp = w_init(); 546ea022d16SRodney W. Grimes ap = (struct tftphdr *)ackbuf; 547ea022d16SRodney W. Grimes block = 0; 548ea022d16SRodney W. Grimes do { 549ea022d16SRodney W. Grimes timeout = 0; 550ea022d16SRodney W. Grimes ap->th_opcode = htons((u_short)ACK); 551ea022d16SRodney W. Grimes ap->th_block = htons((u_short)block); 552ea022d16SRodney W. Grimes block++; 553ea022d16SRodney W. Grimes (void) setjmp(timeoutbuf); 554ea022d16SRodney W. Grimes send_ack: 555ea022d16SRodney W. Grimes if (send(peer, ackbuf, 4, 0) != 4) { 556ea022d16SRodney W. Grimes syslog(LOG_ERR, "tftpd: write: %m\n"); 557ea022d16SRodney W. Grimes goto abort; 558ea022d16SRodney W. Grimes } 559ea022d16SRodney W. Grimes write_behind(file, pf->f_convert); 560ea022d16SRodney W. Grimes for ( ; ; ) { 561ea022d16SRodney W. Grimes alarm(rexmtval); 562ea022d16SRodney W. Grimes n = recv(peer, dp, PKTSIZE, 0); 563ea022d16SRodney W. Grimes alarm(0); 564ea022d16SRodney W. Grimes if (n < 0) { /* really? */ 565ea022d16SRodney W. Grimes syslog(LOG_ERR, "tftpd: read: %m\n"); 566ea022d16SRodney W. Grimes goto abort; 567ea022d16SRodney W. Grimes } 568ea022d16SRodney W. Grimes dp->th_opcode = ntohs((u_short)dp->th_opcode); 569ea022d16SRodney W. Grimes dp->th_block = ntohs((u_short)dp->th_block); 570ea022d16SRodney W. Grimes if (dp->th_opcode == ERROR) 571ea022d16SRodney W. Grimes goto abort; 572ea022d16SRodney W. Grimes if (dp->th_opcode == DATA) { 573ea022d16SRodney W. Grimes if (dp->th_block == block) { 574ea022d16SRodney W. Grimes break; /* normal */ 575ea022d16SRodney W. Grimes } 576ea022d16SRodney W. Grimes /* Re-synchronize with the other side */ 577ea022d16SRodney W. Grimes (void) synchnet(peer); 578ea022d16SRodney W. Grimes if (dp->th_block == (block-1)) 579ea022d16SRodney W. Grimes goto send_ack; /* rexmit */ 580ea022d16SRodney W. Grimes } 581ea022d16SRodney W. Grimes } 582ea022d16SRodney W. Grimes /* size = write(file, dp->th_data, n - 4); */ 583ea022d16SRodney W. Grimes size = writeit(file, &dp, n - 4, pf->f_convert); 584ea022d16SRodney W. Grimes if (size != (n-4)) { /* ahem */ 585ea022d16SRodney W. Grimes if (size < 0) nak(errno + 100); 586ea022d16SRodney W. Grimes else nak(ENOSPACE); 587ea022d16SRodney W. Grimes goto abort; 588ea022d16SRodney W. Grimes } 589ea022d16SRodney W. Grimes } while (size == SEGSIZE); 590ea022d16SRodney W. Grimes write_behind(file, pf->f_convert); 591ea022d16SRodney W. Grimes (void) fclose(file); /* close data file */ 592ea022d16SRodney W. Grimes 593ea022d16SRodney W. Grimes ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 594ea022d16SRodney W. Grimes ap->th_block = htons((u_short)(block)); 595ea022d16SRodney W. Grimes (void) send(peer, ackbuf, 4, 0); 596ea022d16SRodney W. Grimes 597ea022d16SRodney W. Grimes signal(SIGALRM, justquit); /* just quit on timeout */ 598ea022d16SRodney W. Grimes alarm(rexmtval); 599ea022d16SRodney W. Grimes n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 600ea022d16SRodney W. Grimes alarm(0); 601ea022d16SRodney W. Grimes if (n >= 4 && /* if read some data */ 602ea022d16SRodney W. Grimes dp->th_opcode == DATA && /* and got a data block */ 603ea022d16SRodney W. Grimes block == dp->th_block) { /* then my last ack was lost */ 604ea022d16SRodney W. Grimes (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 605ea022d16SRodney W. Grimes } 606ea022d16SRodney W. Grimes abort: 607ea022d16SRodney W. Grimes return; 608ea022d16SRodney W. Grimes } 609ea022d16SRodney W. Grimes 610ea022d16SRodney W. Grimes struct errmsg { 611ea022d16SRodney W. Grimes int e_code; 612ea022d16SRodney W. Grimes char *e_msg; 613ea022d16SRodney W. Grimes } errmsgs[] = { 614ea022d16SRodney W. Grimes { EUNDEF, "Undefined error code" }, 615ea022d16SRodney W. Grimes { ENOTFOUND, "File not found" }, 616ea022d16SRodney W. Grimes { EACCESS, "Access violation" }, 617ea022d16SRodney W. Grimes { ENOSPACE, "Disk full or allocation exceeded" }, 618ea022d16SRodney W. Grimes { EBADOP, "Illegal TFTP operation" }, 619ea022d16SRodney W. Grimes { EBADID, "Unknown transfer ID" }, 620ea022d16SRodney W. Grimes { EEXISTS, "File already exists" }, 621ea022d16SRodney W. Grimes { ENOUSER, "No such user" }, 622ea022d16SRodney W. Grimes { -1, 0 } 623ea022d16SRodney W. Grimes }; 624ea022d16SRodney W. Grimes 625ea022d16SRodney W. Grimes static char * 626ea022d16SRodney W. Grimes errtomsg(error) 627ea022d16SRodney W. Grimes int error; 628ea022d16SRodney W. Grimes { 629ea022d16SRodney W. Grimes static char buf[20]; 630ea022d16SRodney W. Grimes register struct errmsg *pe; 631ea022d16SRodney W. Grimes if (error == 0) 632ea022d16SRodney W. Grimes return "success"; 633ea022d16SRodney W. Grimes for (pe = errmsgs; pe->e_code >= 0; pe++) 634ea022d16SRodney W. Grimes if (pe->e_code == error) 635ea022d16SRodney W. Grimes return pe->e_msg; 636fca08b7cSWarner Losh snprintf(buf, sizeof(buf), "error %d", error); 637ea022d16SRodney W. Grimes return buf; 638ea022d16SRodney W. Grimes } 639ea022d16SRodney W. Grimes 640ea022d16SRodney W. Grimes /* 641ea022d16SRodney W. Grimes * Send a nak packet (error message). 642ea022d16SRodney W. Grimes * Error code passed in is one of the 643ea022d16SRodney W. Grimes * standard TFTP codes, or a UNIX errno 644ea022d16SRodney W. Grimes * offset by 100. 645ea022d16SRodney W. Grimes */ 646ea022d16SRodney W. Grimes static void 647ea022d16SRodney W. Grimes nak(error) 648ea022d16SRodney W. Grimes int error; 649ea022d16SRodney W. Grimes { 650ea022d16SRodney W. Grimes register struct tftphdr *tp; 651ea022d16SRodney W. Grimes int length; 652ea022d16SRodney W. Grimes register struct errmsg *pe; 653ea022d16SRodney W. Grimes 654ea022d16SRodney W. Grimes tp = (struct tftphdr *)buf; 655ea022d16SRodney W. Grimes tp->th_opcode = htons((u_short)ERROR); 656ea022d16SRodney W. Grimes tp->th_code = htons((u_short)error); 657ea022d16SRodney W. Grimes for (pe = errmsgs; pe->e_code >= 0; pe++) 658ea022d16SRodney W. Grimes if (pe->e_code == error) 659ea022d16SRodney W. Grimes break; 660ea022d16SRodney W. Grimes if (pe->e_code < 0) { 661ea022d16SRodney W. Grimes pe->e_msg = strerror(error - 100); 662ea022d16SRodney W. Grimes tp->th_code = EUNDEF; /* set 'undef' errorcode */ 663ea022d16SRodney W. Grimes } 664ea022d16SRodney W. Grimes strcpy(tp->th_msg, pe->e_msg); 665ea022d16SRodney W. Grimes length = strlen(pe->e_msg); 666ea022d16SRodney W. Grimes tp->th_msg[length] = '\0'; 667ea022d16SRodney W. Grimes length += 5; 668ea022d16SRodney W. Grimes if (send(peer, buf, length, 0) != length) 669ea022d16SRodney W. Grimes syslog(LOG_ERR, "nak: %m\n"); 670ea022d16SRodney W. Grimes } 671ea022d16SRodney W. Grimes 672ea022d16SRodney W. Grimes static char * 673ea022d16SRodney W. Grimes verifyhost(fromp) 674ea022d16SRodney W. Grimes struct sockaddr_in *fromp; 675ea022d16SRodney W. Grimes { 676ea022d16SRodney W. Grimes struct hostent *hp; 677ea022d16SRodney W. Grimes 678ea022d16SRodney W. Grimes hp = gethostbyaddr((char *)&fromp->sin_addr, sizeof (fromp->sin_addr), 679ea022d16SRodney W. Grimes fromp->sin_family); 680ea022d16SRodney W. Grimes if (hp) 681ea022d16SRodney W. Grimes return hp->h_name; 682ea022d16SRodney W. Grimes else 683ea022d16SRodney W. Grimes return inet_ntoa(fromp->sin_addr); 684ea022d16SRodney W. Grimes } 685