/* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "config.h" /* Must be included first */ #include "tftpd.h" /* * Trivial file transfer protocol server. * * This version includes many modifications by Jim Guyton */ #include #include #include #include #include #include #include "common/tftpsubs.h" #include "recvfrom.h" #include "remap.h" #ifdef HAVE_SYS_FILIO_H #include /* Necessary for FIONBIO on Solaris */ #endif #ifdef HAVE_TCPWRAPPERS #include int deny_severity = LOG_WARNING; int allow_severity = -1; /* Don't log at all */ static struct request_info wrap_request; #endif #ifdef HAVE_IPV6 static int ai_fam = AF_UNSPEC; #else static int ai_fam = AF_INET; #endif #define TIMEOUT 1000000 /* Default timeout (us) */ #define TRIES 6 /* Number of attempts to send each packet */ #define TIMEOUT_LIMIT ((1 << TRIES)-1) const char *__progname; static int peer; static unsigned long timeout = TIMEOUT; /* Current timeout value */ static unsigned long rexmtval = TIMEOUT; /* Basic timeout value */ static unsigned long maxtimeout = TIMEOUT_LIMIT * TIMEOUT; static int timeout_quit = 0; static sigjmp_buf timeoutbuf; #define PKTSIZE MAX_SEGSIZE+4 static char buf[PKTSIZE]; static char ackbuf[PKTSIZE]; static unsigned int max_blksize = MAX_SEGSIZE; static char tmpbuf[INET6_ADDRSTRLEN], *tmp_p; static union sock_addr from; static socklen_t fromlen; static off_t tsize; static int tsize_ok; static int ndirs; static const char **dirs; static int secure = 0; int cancreate = 0; int unixperms = 0; int portrange = 0; unsigned int portrange_from, portrange_to; int verbosity = 0; struct formats; #ifdef WITH_REGEX static struct rule *rewrite_rules = NULL; #endif int tftp(struct tftphdr *, int); static void nak(int, const char *); static void timer(int); static void do_opt(char *, char *, char **); static int set_blksize(char *, char **); static int set_blksize2(char *, char **); static int set_tsize(char *, char **); static int set_timeout(char *, char **); static int set_utimeout(char *, char **); struct options { const char *o_opt; int (*o_fnc) (char *, char **); } options[] = { {"blksize", set_blksize}, {"blksize2", set_blksize2}, {"tsize", set_tsize}, {"timeout", set_timeout}, {"utimeout", set_utimeout}, {NULL, NULL} }; /* Simple handler for SIGHUP */ static volatile sig_atomic_t caught_sighup = 0; static void handle_sighup(int sig) { (void)sig; /* Suppress unused warning */ caught_sighup = 1; } /* Handle timeout signal or timeout event */ void timer(int sig) { (void)sig; /* Suppress unused warning */ timeout <<= 1; if (timeout >= maxtimeout || timeout_quit) exit(0); siglongjmp(timeoutbuf, 1); } #ifdef WITH_REGEX static struct rule *read_remap_rules(const char *file) { FILE *f; struct rule *rulep; f = fopen(file, "rt"); if (!f) { syslog(LOG_ERR, "Cannot open map file: %s: %m", file); exit(EX_NOINPUT); } rulep = parserulefile(f); fclose(f); return rulep; } #endif static void set_socket_nonblock(int fd, int flag) { int err; int flags; #if defined(HAVE_FCNTL) && defined(HAVE_O_NONBLOCK_DEFINITION) /* Posixly correct */ err = ((flags = fcntl(fd, F_GETFL, 0)) < 0) || (fcntl (fd, F_SETFL, flag ? flags | O_NONBLOCK : flags & ~O_NONBLOCK) < 0); #else flags = flag ? 1 : 0; err = (ioctl(fd, FIONBIO, &flags) < 0); #endif if (err) { syslog(LOG_ERR, "Cannot set nonblock flag on socket: %m"); exit(EX_OSERR); } } static void pmtu_discovery_off(int fd) { #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) int pmtu = IP_PMTUDISC_DONT; setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); #endif } /* * Receive packet with synchronous timeout; timeout is adjusted * to account for time spent waiting. */ static int recv_time(int s, void *rbuf, int len, unsigned int flags, unsigned long *timeout_us_p) { fd_set fdset; struct timeval tmv, t0, t1; int rv, err; unsigned long timeout_us = *timeout_us_p; unsigned long timeout_left, dt; gettimeofday(&t0, NULL); timeout_left = timeout_us; for (;;) { FD_ZERO(&fdset); FD_SET(s, &fdset); do { tmv.tv_sec = timeout_left / 1000000; tmv.tv_usec = timeout_left % 1000000; rv = select(s + 1, &fdset, NULL, NULL, &tmv); err = errno; gettimeofday(&t1, NULL); dt = (t1.tv_sec - t0.tv_sec) * 1000000 + (t1.tv_usec - t0.tv_usec); *timeout_us_p = timeout_left = (dt >= timeout_us) ? 1 : (timeout_us - dt); } while (rv == -1 && err == EINTR); if (rv == 0) { timer(0); /* Should not return */ return -1; } set_socket_nonblock(s, 1); rv = recv(s, rbuf, len, flags); err = errno; set_socket_nonblock(s, 0); if (rv < 0) { if (E_WOULD_BLOCK(err) || err == EINTR) { continue; /* Once again, with feeling... */ } else { errno = err; return rv; } } else { return rv; } } } static int split_port(char **ap, char **pp) { char *a, *p; int ret = AF_UNSPEC; a = *ap; #ifdef HAVE_IPV6 if (is_numeric_ipv6(a)) { if (*a++ != '[') return -1; *ap = a; p = strrchr(a, ']'); if (!p) return -1; *p++ = 0; a = p; ret = AF_INET6; p = strrchr(a, ':'); if (p) *p++ = 0; } else #endif { struct in_addr in; p = strrchr(a, ':'); if (p) *p++ = 0; if (inet_aton(a, &in)) ret = AF_INET; } *pp = p; return ret; } enum long_only_options { OPT_VERBOSITY = 256, }; static struct option long_options[] = { { "ipv4", 0, NULL, '4' }, { "ipv6", 0, NULL, '6' }, { "create", 0, NULL, 'c' }, { "secure", 0, NULL, 's' }, { "permissive", 0, NULL, 'p' }, { "verbose", 0, NULL, 'v' }, { "verbosity", 1, NULL, OPT_VERBOSITY }, { "version", 0, NULL, 'V' }, { "listen", 0, NULL, 'l' }, { "foreground", 0, NULL, 'L' }, { "address", 1, NULL, 'a' }, { "blocksize", 1, NULL, 'B' }, { "user", 1, NULL, 'u' }, { "umask", 1, NULL, 'U' }, { "refuse", 1, NULL, 'r' }, { "timeout", 1, NULL, 't' }, { "retransmit", 1, NULL, 'T' }, { "port-range", 1, NULL, 'R' }, { "map-file", 1, NULL, 'm' }, { NULL, 0, NULL, 0 } }; static const char short_options[] = "46cspvVlLa:B:u:U:r:t:T:R:m:"; int main(int argc, char **argv) { struct tftphdr *tp; struct passwd *pw; struct options *opt; union sock_addr myaddr; struct sockaddr_in bindaddr4; #ifdef HAVE_IPV6 struct sockaddr_in6 bindaddr6; int force_ipv6 = 0; #endif int n; int fd = -1; int fd4 = -1; int fd6 = -1; int fdmax = 0; int standalone = 0; /* Standalone (listen) mode */ int nodaemon = 0; /* Do not detach process */ char *address = NULL; /* Address to listen to */ pid_t pid; mode_t my_umask = 0; int spec_umask = 0; int c; int setrv; int waittime = 900; /* Default time to wait for a connect */ const char *user = "nobody"; /* Default user */ char *p, *ep; #ifdef WITH_REGEX char *rewrite_file = NULL; #endif u_short tp_opcode; /* basename() is way too much of a pain from a portability standpoint */ p = strrchr(argv[0], '/'); __progname = (p && p[1]) ? p + 1 : argv[0]; openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); srand(time(NULL) ^ getpid()); while ((c = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) switch (c) { case '4': ai_fam = AF_INET; break; #ifdef HAVE_IPV6 case '6': ai_fam = AF_INET6; force_ipv6 = 1; break; #endif case 'c': cancreate = 1; break; case 's': secure = 1; break; case 'p': unixperms = 1; break; case 'l': standalone = 1; break; case 'L': standalone = 1; nodaemon = 1; break; case 'a': address = optarg; break; case 't': waittime = atoi(optarg); break; case 'B': { char *vp; max_blksize = (unsigned int)strtoul(optarg, &vp, 10); if (max_blksize < 512 || max_blksize > MAX_SEGSIZE || *vp) { syslog(LOG_ERR, "Bad maximum blocksize value (range 512-%d): %s", MAX_SEGSIZE, optarg); exit(EX_USAGE); } } break; case 'T': { char *vp; unsigned long tov = strtoul(optarg, &vp, 10); if (tov < 10000UL || tov > 255000000UL || *vp) { syslog(LOG_ERR, "Bad timeout value: %s", optarg); exit(EX_USAGE); } rexmtval = timeout = tov; maxtimeout = rexmtval * TIMEOUT_LIMIT; } break; case 'R': { if (sscanf(optarg, "%u:%u", &portrange_from, &portrange_to) != 2 || portrange_from > portrange_to || portrange_to >= 65535) { syslog(LOG_ERR, "Bad port range: %s", optarg); exit(EX_USAGE); } portrange = 1; } break; case 'u': user = optarg; break; case 'U': my_umask = strtoul(optarg, &ep, 8); if (*ep) { syslog(LOG_ERR, "Invalid umask: %s", optarg); exit(EX_USAGE); } spec_umask = 1; break; case 'r': for (opt = options; opt->o_opt; opt++) { if (!strcasecmp(optarg, opt->o_opt)) { opt->o_opt = ""; /* Don't support this option */ break; } } if (!opt->o_opt) { syslog(LOG_ERR, "Unknown option: %s", optarg); exit(EX_USAGE); } break; #ifdef WITH_REGEX case 'm': if (rewrite_file) { syslog(LOG_ERR, "Multiple -m options"); exit(EX_USAGE); } rewrite_file = optarg; break; #endif case 'v': verbosity++; break; case OPT_VERBOSITY: verbosity = atoi(optarg); break; case 'V': /* Print configuration to stdout and exit */ printf("%s\n", TFTPD_CONFIG_STR); exit(0); break; default: syslog(LOG_ERR, "Unknown option: '%c'", optopt); break; } dirs = xmalloc((argc - optind + 1) * sizeof(char *)); for (ndirs = 0; optind != argc; optind++) dirs[ndirs++] = argv[optind]; dirs[ndirs] = NULL; if (secure) { if (ndirs == 0) { syslog(LOG_ERR, "no -s directory"); exit(EX_USAGE); } if (ndirs > 1) { syslog(LOG_ERR, "too many -s directories"); exit(EX_USAGE); } if (chdir(dirs[0])) { syslog(LOG_ERR, "%s: %m", dirs[0]); exit(EX_NOINPUT); } } pw = getpwnam(user); if (!pw) { syslog(LOG_ERR, "no user %s: %m", user); exit(EX_NOUSER); } if (spec_umask || !unixperms) umask(my_umask); #ifdef WITH_REGEX if (rewrite_file) rewrite_rules = read_remap_rules(rewrite_file); #endif /* If we're running standalone, set up the input port */ if (standalone) { #ifdef HAVE_IPV6 if (ai_fam != AF_INET6) { #endif fd4 = socket(AF_INET, SOCK_DGRAM, 0); if (fd4 < 0) { syslog(LOG_ERR, "cannot open IPv4 socket: %m"); exit(EX_OSERR); } #ifndef __CYGWIN__ set_socket_nonblock(fd4, 1); #endif memset(&bindaddr4, 0, sizeof bindaddr4); bindaddr4.sin_family = AF_INET; bindaddr4.sin_addr.s_addr = INADDR_ANY; bindaddr4.sin_port = htons(IPPORT_TFTP); #ifdef HAVE_IPV6 } if (ai_fam != AF_INET) { fd6 = socket(AF_INET6, SOCK_DGRAM, 0); if (fd6 < 0) { if (fd4 < 0) { syslog(LOG_ERR, "cannot open IPv6 socket: %m"); exit(EX_OSERR); } else { syslog(LOG_ERR, "cannot open IPv6 socket, disable IPv6: %m"); } } #ifndef __CYGWIN__ set_socket_nonblock(fd6, 1); #endif memset(&bindaddr6, 0, sizeof bindaddr6); bindaddr6.sin6_family = AF_INET6; bindaddr6.sin6_port = htons(IPPORT_TFTP); } #endif if (address) { char *portptr = NULL, *eportptr; int err; struct servent *servent; unsigned long port; address = tfstrdup(address); err = split_port(&address, &portptr); switch (err) { case AF_INET: #ifdef HAVE_IPV6 if (fd6 >= 0) { close(fd6); fd6 = -1; if (ai_fam == AF_INET6) { syslog(LOG_ERR, "Address %s is not in address family AF_INET6", address); exit(EX_USAGE); } ai_fam = AF_INET; } break; case AF_INET6: if (fd4 >= 0) { close(fd4); fd4 = -1; if (ai_fam == AF_INET) { syslog(LOG_ERR, "Address %s is not in address family AF_INET", address); exit(EX_USAGE); } ai_fam = AF_INET6; } break; #endif case AF_UNSPEC: break; default: syslog(LOG_ERR, "Numeric IPv6 addresses need to be enclosed in []"); exit(EX_USAGE); } if (!portptr) portptr = (char *)"tftp"; if (*address) { if (fd4 >= 0) { bindaddr4.sin_family = AF_INET; err = set_sock_addr(address, (union sock_addr *)&bindaddr4, NULL); if (err) { syslog(LOG_ERR, "cannot resolve local IPv4 bind address: %s, %s", address, gai_strerror(err)); exit(EX_NOINPUT); } } #ifdef HAVE_IPV6 if (fd6 >= 0) { bindaddr6.sin6_family = AF_INET6; err = set_sock_addr(address, (union sock_addr *)&bindaddr6, NULL); if (err) { if (fd4 >= 0) { syslog(LOG_ERR, "cannot resolve local IPv6 bind address: %s" "(%s); using IPv4 only", address, gai_strerror(err)); close(fd6); fd6 = -1; } else { syslog(LOG_ERR, "cannot resolve local IPv6 bind address: %s" "(%s)", address, gai_strerror(err)); exit(EX_NOINPUT); } } } #endif } else { /* Default to using INADDR_ANY */ } if (portptr && *portptr) { servent = getservbyname(portptr, "udp"); if (servent) { if (fd4 >= 0) bindaddr4.sin_port = servent->s_port; #ifdef HAVE_IPV6 if (fd6 >= 0) bindaddr6.sin6_port = servent->s_port; #endif } else if ((port = strtoul(portptr, &eportptr, 0)) && !*eportptr) { if (fd4 >= 0) bindaddr4.sin_port = htons(port); #ifdef HAVE_IPV6 if (fd6 >= 0) bindaddr6.sin6_port = htons(port); #endif } else if (!strcmp(portptr, "tftp")) { /* It's TFTP, we're OK */ } else { syslog(LOG_ERR, "cannot resolve local bind port: %s", portptr); exit(EX_NOINPUT); } } } if (fd4 >= 0) { if (bind(fd4, (struct sockaddr *)&bindaddr4, sizeof(bindaddr4)) < 0) { syslog(LOG_ERR, "cannot bind to local IPv4 socket: %m"); exit(EX_OSERR); } } #ifdef HAVE_IPV6 if (fd6 >= 0) { #if defined(IPV6_V6ONLY) int on = 1; if (fd4 >= 0 || force_ipv6) if (setsockopt(fd6, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&on, sizeof(on))) syslog(LOG_ERR, "cannot setsockopt IPV6_V6ONLY %m"); #endif if (bind(fd6, (struct sockaddr *)&bindaddr6, sizeof(bindaddr6)) < 0) { if (fd4 >= 0) { syslog(LOG_ERR, "cannot bind to local IPv6 socket," "IPv6 disabled: %m"); close(fd6); fd6 = -1; } else { syslog(LOG_ERR, "cannot bind to local IPv6 socket: %m"); exit(EX_OSERR); } } } #endif /* Daemonize this process */ /* Note: when running in secure mode (-s), we must not chroot, since we are already in the proper directory. */ if (!nodaemon && daemon(secure, 0) < 0) { syslog(LOG_ERR, "cannot daemonize: %m"); exit(EX_OSERR); } if (fd6 > fd4) fdmax = fd6; else fdmax = fd4; } else { /* 0 is our socket descriptor */ close(1); close(2); fd = 0; fdmax = 0; /* Note: on Cygwin, select() on a nonblocking socket becomes a nonblocking select. */ #ifndef __CYGWIN__ set_socket_nonblock(fd, 1); #endif } /* Disable path MTU discovery */ pmtu_discovery_off(0); /* This means we don't want to wait() for children */ #ifdef SA_NOCLDWAIT set_signal(SIGCHLD, SIG_IGN, SA_NOCLDSTOP | SA_NOCLDWAIT); #else set_signal(SIGCHLD, SIG_IGN, SA_NOCLDSTOP); #endif /* Take SIGHUP and use it to set a variable. This is polled synchronously to make sure we don't lose packets as a result. */ set_signal(SIGHUP, handle_sighup, 0); while (1) { fd_set readset; struct timeval tv_waittime; int rv; if (caught_sighup) { caught_sighup = 0; if (standalone) { #ifdef WITH_REGEX if (rewrite_file) { freerules(rewrite_rules); rewrite_rules = read_remap_rules(rewrite_file); } #endif } else { /* Return to inetd for respawn */ exit(0); } } FD_ZERO(&readset); if (standalone) { if (fd4 >= 0) { FD_SET(fd4, &readset); #ifdef __CYGWIN__ /* On Cygwin, select() on a nonblocking socket returns immediately, with a rv of 0! */ set_socket_nonblock(fd4, 0); #endif } if (fd6 >= 0) { FD_SET(fd6, &readset); #ifdef __CYGWIN__ /* On Cygwin, select() on a nonblocking socket returns immediately, with a rv of 0! */ set_socket_nonblock(fd6, 0); #endif } } else { /* fd always 0 */ fd = 0; #ifdef __CYGWIN__ /* On Cygwin, select() on a nonblocking socket returns immediately, with a rv of 0! */ set_socket_nonblock(fd, 0); #endif FD_SET(fd, &readset); } tv_waittime.tv_sec = waittime; tv_waittime.tv_usec = 0; /* Never time out if we're in standalone mode */ rv = select(fdmax + 1, &readset, NULL, NULL, standalone ? NULL : &tv_waittime); if (rv == -1 && errno == EINTR) continue; /* Signal caught, reloop */ if (rv == -1) { syslog(LOG_ERR, "select loop: %m"); exit(EX_IOERR); } else if (rv == 0) { exit(0); /* Timeout, return to inetd */ } if (standalone) { if ((fd4 >= 0) && FD_ISSET(fd4, &readset)) fd = fd4; else if ((fd6 >= 0) && FD_ISSET(fd6, &readset)) fd = fd6; else /* not in set ??? */ continue; } #ifdef __CYGWIN__ /* On Cygwin, select() on a nonblocking socket returns immediately, with a rv of 0! */ set_socket_nonblock(fd, 0); #endif fromlen = sizeof(from); n = myrecvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen, &myaddr); if (n < 0) { if (E_WOULD_BLOCK(errno) || errno == EINTR) { continue; /* Again, from the top */ } else { syslog(LOG_ERR, "recvfrom: %m"); exit(EX_IOERR); } } #ifdef HAVE_IPV6 if ((from.sa.sa_family != AF_INET) && (from.sa.sa_family != AF_INET6)) { syslog(LOG_ERR, "received address was not AF_INET/AF_INET6," " please check your inetd config"); #else if (from.sa.sa_family != AF_INET) { syslog(LOG_ERR, "received address was not AF_INET," " please check your inetd config"); #endif exit(EX_PROTOCOL); } if (standalone) { if ((from.sa.sa_family == AF_INET) && (myaddr.si.sin_addr.s_addr == INADDR_ANY)) { /* myrecvfrom() didn't capture the source address; but we might have bound to a specific address, if so we should use it */ memcpy(SOCKADDR_P(&myaddr), &bindaddr4.sin_addr, sizeof(bindaddr4.sin_addr)); #ifdef HAVE_IPV6 } else if ((from.sa.sa_family == AF_INET6) && IN6_IS_ADDR_UNSPECIFIED(SOCKADDR_P(&myaddr))) { memcpy(SOCKADDR_P(&myaddr), &bindaddr6.sin6_addr, sizeof(bindaddr6.sin6_addr)); #endif } } /* * Now that we have read the request packet from the UDP * socket, we fork and go back to listening to the socket. */ pid = fork(); if (pid < 0) { syslog(LOG_ERR, "fork: %m"); exit(EX_OSERR); /* Return to inetd, just in case */ } else if (pid == 0) break; /* Child exit, parent loop */ } /* Child process: handle the actual request here */ /* Ignore SIGHUP */ set_signal(SIGHUP, SIG_IGN, 0); #ifdef HAVE_TCPWRAPPERS /* Verify if this was a legal request for us. This has to be done before the chroot, while /etc is still accessible. */ request_init(&wrap_request, RQ_DAEMON, __progname, RQ_FILE, fd, RQ_CLIENT_SIN, &from, RQ_SERVER_SIN, &myaddr, 0); sock_methods(&wrap_request); tmp_p = (char *)inet_ntop(myaddr.sa.sa_family, SOCKADDR_P(&myaddr), tmpbuf, INET6_ADDRSTRLEN); if (!tmp_p) { tmp_p = tmpbuf; strcpy(tmpbuf, "???"); } if (hosts_access(&wrap_request) == 0) { if (deny_severity != -1) syslog(deny_severity, "connection refused from %s", tmp_p); exit(EX_NOPERM); /* Access denied */ } else if (allow_severity != -1) { syslog(allow_severity, "connect from %s", tmp_p); } #endif /* Close file descriptors we don't need */ close(fd); /* Get a socket. This has to be done before the chroot(), since some systems require access to /dev to create a socket. */ peer = socket(myaddr.sa.sa_family, SOCK_DGRAM, 0); if (peer < 0) { syslog(LOG_ERR, "socket: %m"); exit(EX_IOERR); } /* Set up the supplementary group access list if possible */ /* /etc/group still need to be accessible at this point */ #ifdef HAVE_INITGROUPS setrv = initgroups(user, pw->pw_gid); if (setrv) { syslog(LOG_ERR, "cannot set groups for user %s", user); exit(EX_OSERR); } #else #ifdef HAVE_SETGROUPS if (setgroups(0, NULL)) { syslog(LOG_ERR, "cannot clear group list"); } #endif #endif /* Chroot and drop privileges */ if (secure) { if (chroot(".")) { syslog(LOG_ERR, "chroot: %m"); exit(EX_OSERR); } #ifdef __CYGWIN__ chdir("/"); /* Cygwin chroot() bug workaround */ #endif } #ifdef HAVE_SETREGID setrv = setregid(pw->pw_gid, pw->pw_gid); #else setrv = setegid(pw->pw_gid) || setgid(pw->pw_gid); #endif #ifdef HAVE_SETREUID setrv = setrv || setreuid(pw->pw_uid, pw->pw_uid); #else /* Important: setuid() must come first */ setrv = setrv || setuid(pw->pw_uid) || (geteuid() != pw->pw_uid && seteuid(pw->pw_uid)); #endif if (setrv) { syslog(LOG_ERR, "cannot drop privileges: %m"); exit(EX_OSERR); } /* Process the request... */ if (pick_port_bind(peer, &myaddr, portrange_from, portrange_to) < 0) { syslog(LOG_ERR, "bind: %m"); exit(EX_IOERR); } if (connect(peer, &from.sa, SOCKLEN(&from)) < 0) { syslog(LOG_ERR, "connect: %m"); exit(EX_IOERR); } /* Disable path MTU discovery */ pmtu_discovery_off(0); tp = (struct tftphdr *)buf; tp_opcode = ntohs(tp->th_opcode); if (tp_opcode == RRQ || tp_opcode == WRQ) tftp(tp, n); exit(0); } static char *rewrite_access(char *, int, const char **); static int validate_access(char *, int, struct formats *, const char **); static void tftp_sendfile(struct formats *, struct tftphdr *, int); static void tftp_recvfile(struct formats *, struct tftphdr *, int); struct formats { const char *f_mode; char *(*f_rewrite) (char *, int, const char **); int (*f_validate) (char *, int, struct formats *, const char **); void (*f_send) (struct formats *, struct tftphdr *, int); void (*f_recv) (struct formats *, struct tftphdr *, int); int f_convert; } formats[] = { { "netascii", rewrite_access, validate_access, tftp_sendfile, tftp_recvfile, 1}, { "octet", rewrite_access, validate_access, tftp_sendfile, tftp_recvfile, 0}, { NULL, NULL, NULL, NULL, NULL, 0} }; /* * Handle initial connection protocol. */ int tftp(struct tftphdr *tp, int size) { char *cp, *end; int argn, ecode; struct formats *pf = NULL; char *origfilename; char *filename, *mode = NULL; const char *errmsgptr; u_short tp_opcode = ntohs(tp->th_opcode); char *val = NULL, *opt = NULL; char *ap = ackbuf + 2; ((struct tftphdr *)ackbuf)->th_opcode = htons(OACK); origfilename = cp = (char *)&(tp->th_stuff); argn = 0; end = (char *)tp + size; while (cp < end && *cp) { do { cp++; } while (cp < end && *cp); if (*cp) { nak(EBADOP, "Request not null-terminated"); exit(0); } argn++; if (argn == 1) { mode = ++cp; } else if (argn == 2) { for (cp = mode; *cp; cp++) *cp = tolower(*cp); for (pf = formats; pf->f_mode; pf++) { if (!strcmp(pf->f_mode, mode)) break; } if (!pf->f_mode) { nak(EBADOP, "Unknown mode"); exit(0); } if (!(filename = (*pf->f_rewrite) (origfilename, tp_opcode, &errmsgptr))) { nak(EACCESS, errmsgptr); /* File denied by mapping rule */ exit(0); } if (verbosity >= 1) { tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from), tmpbuf, INET6_ADDRSTRLEN); if (!tmp_p) { tmp_p = tmpbuf; strcpy(tmpbuf, "???"); } if (filename == origfilename || !strcmp(filename, origfilename)) syslog(LOG_NOTICE, "%s from %s filename %s\n", tp_opcode == WRQ ? "WRQ" : "RRQ", tmp_p, filename); else syslog(LOG_NOTICE, "%s from %s filename %s remapped to %s\n", tp_opcode == WRQ ? "WRQ" : "RRQ", tmp_p, origfilename, filename); } ecode = (*pf->f_validate) (filename, tp_opcode, pf, &errmsgptr); if (ecode) { nak(ecode, errmsgptr); exit(0); } opt = ++cp; } else if (argn & 1) { val = ++cp; } else { do_opt(opt, val, &ap); opt = ++cp; } } if (!pf) { nak(EBADOP, "Missing mode"); exit(0); } if (ap != (ackbuf + 2)) { if (tp_opcode == WRQ) (*pf->f_recv) (pf, (struct tftphdr *)ackbuf, ap - ackbuf); else (*pf->f_send) (pf, (struct tftphdr *)ackbuf, ap - ackbuf); } else { if (tp_opcode == WRQ) (*pf->f_recv) (pf, NULL, 0); else (*pf->f_send) (pf, NULL, 0); } exit(0); /* Request completed */ } static int blksize_set; /* * Set a non-standard block size (c.f. RFC2348) */ static int set_blksize(char *val, char **ret) { static char b_ret[6]; unsigned int sz; char *vend; sz = (unsigned int)strtoul(val, &vend, 10); if (blksize_set || *vend) return 0; if (sz < 8) return (0); else if (sz > max_blksize) sz = max_blksize; segsize = sz; sprintf(*ret = b_ret, "%u", sz); blksize_set = 1; return (1); } /* * Set a power-of-two block size (nonstandard) */ static int set_blksize2(char *val, char **ret) { static char b_ret[6]; unsigned int sz; char *vend; sz = (unsigned int)strtoul(val, &vend, 10); if (blksize_set || *vend) return 0; if (sz < 8) return (0); else if (sz > max_blksize) sz = max_blksize; /* Convert to a power of two */ if (sz & (sz - 1)) { unsigned int sz1 = 1; /* Not a power of two - need to convert */ while (sz >>= 1) sz1 <<= 1; sz = sz1; } segsize = sz; sprintf(*ret = b_ret, "%u", sz); blksize_set = 1; return (1); } /* * Return a file size (c.f. RFC2349) * For netascii mode, we don't know the size ahead of time; * so reject the option. */ static int set_tsize(char *val, char **ret) { static char b_ret[sizeof(uintmax_t) * CHAR_BIT / 3 + 2]; uintmax_t sz; char *vend; sz = strtoumax(val, &vend, 10); if (!tsize_ok || *vend) return 0; if (sz == 0) sz = (uintmax_t) tsize; sprintf(*ret = b_ret, "%" PRIuMAX, sz); return (1); } /* * Set the timeout (c.f. RFC2349). This is supposed * to be the (default) retransmission timeout, but being an * integer in seconds it seems a bit limited. */ static int set_timeout(char *val, char **ret) { static char b_ret[4]; unsigned long to; char *vend; to = strtoul(val, &vend, 10); if (to < 1 || to > 255 || *vend) return 0; rexmtval = timeout = to * 1000000UL; maxtimeout = rexmtval * TIMEOUT_LIMIT; sprintf(*ret = b_ret, "%lu", to); return (1); } /* Similar, but in microseconds. We allow down to 10 ms. */ static int set_utimeout(char *val, char **ret) { static char b_ret[4]; unsigned long to; char *vend; to = strtoul(val, &vend, 10); if (to < 10000UL || to > 255000000UL || *vend) return 0; rexmtval = timeout = to; maxtimeout = rexmtval * TIMEOUT_LIMIT; sprintf(*ret = b_ret, "%lu", to); return (1); } /* * Parse RFC2347 style options */ static void do_opt(char *opt, char *val, char **ap) { struct options *po; char *ret; /* Global option-parsing variables initialization */ blksize_set = 0; if (!*opt) return; for (po = options; po->o_opt; po++) if (!strcasecmp(po->o_opt, opt)) { if (po->o_fnc(val, &ret)) { if (*ap + strlen(opt) + strlen(ret) + 2 >= ackbuf + sizeof(ackbuf)) { nak(EOPTNEG, "Insufficient space for options"); exit(0); } *ap = strrchr(strcpy(strrchr(strcpy(*ap, opt), '\0') + 1, ret), '\0') + 1; } else { nak(EOPTNEG, "Unsupported option(s) requested"); exit(0); } break; } return; } #ifdef WITH_REGEX /* * This is called by the remap engine when it encounters macros such * as \i. It should write the output in "output" if non-NULL, and * return the length of the output (generated or not). * * Return -1 on failure. */ static int rewrite_macros(char macro, char *output) { char *p, tb[INET6_ADDRSTRLEN]; int l=0; switch (macro) { case 'i': p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from), tb, INET6_ADDRSTRLEN); if (output && p) strcpy(output, p); if (!p) return 0; else return strlen(p); case 'x': if (output) { if (from.sa.sa_family == AF_INET) { sprintf(output, "%08lX", (unsigned long)ntohl(from.si.sin_addr.s_addr)); l = 8; #ifdef HAVE_IPV6 } else { unsigned char *c = (unsigned char *)SOCKADDR_P(&from); p = tb; for (l = 0; l < 16; l++) { sprintf(p, "%02X", *c); c++; p += 2; } strcpy(output, tb); l = strlen(tb); #endif } } return l; default: return -1; } } /* * Modify the filename, if applicable. If it returns NULL, deny the access. */ static char *rewrite_access(char *filename, int mode, const char **msg) { if (rewrite_rules) { char *newname = rewrite_string(filename, rewrite_rules, mode != RRQ, rewrite_macros, msg); filename = newname; } return filename; } #else static char *rewrite_access(char *filename, int mode, const char **msg) { (void)mode; /* Avoid warning */ (void)msg; return filename; } #endif static FILE *file; /* * Validate file access. Since we * have no uid or gid, for now require * file to exist and be publicly * readable/writable, unless -p specified. * If we were invoked with arguments * from inetd then the file must also be * in one of the given directory prefixes. * Note also, full path name must be * given as we have no login directory. */ static int validate_access(char *filename, int mode, struct formats *pf, const char **errmsg) { struct stat stbuf; int i, len; int fd, wmode, rmode; char *cp; const char **dirp; char stdio_mode[3]; tsize_ok = 0; *errmsg = NULL; if (!secure) { if (*filename != '/') { *errmsg = "Only absolute filenames allowed"; return (EACCESS); } /* * prevent tricksters from getting around the directory * restrictions */ len = strlen(filename); for (i = 1; i < len - 3; i++) { cp = filename + i; if (*cp == '.' && memcmp(cp - 1, "/../", 4) == 0) { *errmsg = "Reverse path not allowed"; return (EACCESS); } } for (dirp = dirs; *dirp; dirp++) if (strncmp(filename, *dirp, strlen(*dirp)) == 0) break; if (*dirp == 0 && dirp != dirs) { *errmsg = "Forbidden directory"; return (EACCESS); } } /* * We use different a different permissions scheme if `cancreate' is * set. */ wmode = O_WRONLY | (cancreate ? O_CREAT : 0) | (unixperms ? O_TRUNC : 0) | (pf->f_convert ? O_TEXT : O_BINARY); rmode = O_RDONLY | (pf->f_convert ? O_TEXT : O_BINARY); fd = open(filename, mode == RRQ ? rmode : wmode, 0666); if (fd < 0) { switch (errno) { case ENOENT: case ENOTDIR: return ENOTFOUND; case ENOSPC: return ENOSPACE; case EEXIST: return EEXISTS; default: return errno + 100; } } if (fstat(fd, &stbuf) < 0) exit(EX_OSERR); /* This shouldn't happen */ if (mode == RRQ) { if (!unixperms && (stbuf.st_mode & (S_IREAD >> 6)) == 0) { *errmsg = "File must have global read permissions"; return (EACCESS); } tsize = stbuf.st_size; /* We don't know the tsize if conversion is needed */ tsize_ok = !pf->f_convert; } else { if (!unixperms) { if ((stbuf.st_mode & (S_IWRITE >> 6)) == 0) { *errmsg = "File must have global write permissions"; return (EACCESS); } /* We didn't get to truncate the file at open() time */ #ifdef HAVE_FTRUNCATE if (ftruncate(fd, (off_t) 0)) { *errmsg = "Cannot reset file size"; return (EACCESS); } #endif } tsize = 0; tsize_ok = 1; } stdio_mode[0] = (mode == RRQ) ? 'r' : 'w'; stdio_mode[1] = (pf->f_convert) ? 't' : 'b'; stdio_mode[2] = '\0'; file = fdopen(fd, stdio_mode); if (file == NULL) exit(EX_OSERR); /* Internal error */ return (0); } /* * Send the requested file. */ static void tftp_sendfile(struct formats *pf, struct tftphdr *oap, int oacklen) { struct tftphdr *dp; struct tftphdr *ap; /* ack packet */ static u_short block = 1; /* Static to avoid longjmp funnies */ u_short ap_opcode, ap_block; unsigned long r_timeout; int size, n; if (oap) { timeout = rexmtval; (void)sigsetjmp(timeoutbuf, 1); oack: r_timeout = timeout; if (send(peer, oap, oacklen, 0) != oacklen) { syslog(LOG_WARNING, "tftpd: oack: %m\n"); goto abort; } for (;;) { n = recv_time(peer, ackbuf, sizeof(ackbuf), 0, &r_timeout); if (n < 0) { syslog(LOG_WARNING, "tftpd: read: %m\n"); goto abort; } ap = (struct tftphdr *)ackbuf; ap_opcode = ntohs((u_short) ap->th_opcode); ap_block = ntohs((u_short) ap->th_block); if (ap_opcode == ERROR) { syslog(LOG_WARNING, "tftp: client does not accept options\n"); goto abort; } if (ap_opcode == ACK) { if (ap_block == 0) break; /* Resynchronize with the other side */ (void)synchnet(peer); goto oack; } } } dp = r_init(); do { size = readit(file, &dp, pf->f_convert); if (size < 0) { nak(errno + 100, NULL); goto abort; } dp->th_opcode = htons((u_short) DATA); dp->th_block = htons((u_short) block); timeout = rexmtval; (void)sigsetjmp(timeoutbuf, 1); r_timeout = timeout; if (send(peer, dp, size + 4, 0) != size + 4) { syslog(LOG_WARNING, "tftpd: write: %m"); goto abort; } read_ahead(file, pf->f_convert); for (;;) { n = recv_time(peer, ackbuf, sizeof(ackbuf), 0, &r_timeout); if (n < 0) { syslog(LOG_WARNING, "tftpd: read(ack): %m"); goto abort; } ap = (struct tftphdr *)ackbuf; ap_opcode = ntohs((u_short) ap->th_opcode); ap_block = ntohs((u_short) ap->th_block); if (ap_opcode == ERROR) goto abort; if (ap_opcode == ACK) { if (ap_block == block) { break; } /* Re-synchronize with the other side */ (void)synchnet(peer); /* * RFC1129/RFC1350: We MUST NOT re-send the DATA * packet in response to an invalid ACK. Doing so * would cause the Sorcerer's Apprentice bug. */ } } block++; } while (size == segsize); abort: (void)fclose(file); } /* * Receive a file. */ static void tftp_recvfile(struct formats *pf, struct tftphdr *oap, int oacklen) { struct tftphdr *dp; int n, size; /* These are "static" to avoid longjmp funnies */ static struct tftphdr *ap; /* ack buffer */ static u_short block = 0; static int acksize; u_short dp_opcode, dp_block; unsigned long r_timeout; dp = w_init(); do { timeout = rexmtval; if (!block && oap) { ap = (struct tftphdr *)ackbuf; acksize = oacklen; } else { ap = (struct tftphdr *)ackbuf; ap->th_opcode = htons((u_short) ACK); ap->th_block = htons((u_short) block); acksize = 4; } block++; (void)sigsetjmp(timeoutbuf, 1); send_ack: r_timeout = timeout; if (send(peer, ackbuf, acksize, 0) != acksize) { syslog(LOG_WARNING, "tftpd: write(ack): %m"); goto abort; } write_behind(file, pf->f_convert); for (;;) { n = recv_time(peer, dp, PKTSIZE, 0, &r_timeout); if (n < 0) { /* really? */ syslog(LOG_WARNING, "tftpd: read: %m"); goto abort; } dp_opcode = ntohs((u_short) dp->th_opcode); dp_block = ntohs((u_short) dp->th_block); if (dp_opcode == ERROR) goto abort; if (dp_opcode == DATA) { if (dp_block == block) { break; /* normal */ } /* Re-synchronize with the other side */ (void)synchnet(peer); if (dp_block == (block - 1)) goto send_ack; /* rexmit */ } } /* size = write(file, dp->th_data, n - 4); */ size = writeit(file, &dp, n - 4, pf->f_convert); if (size != (n - 4)) { /* ahem */ if (size < 0) nak(errno + 100, NULL); else nak(ENOSPACE, NULL); goto abort; } } while (size == segsize); write_behind(file, pf->f_convert); (void)fclose(file); /* close data file */ ap->th_opcode = htons((u_short) ACK); /* send the "final" ack */ ap->th_block = htons((u_short) (block)); (void)send(peer, ackbuf, 4, 0); timeout_quit = 1; /* just quit on timeout */ n = recv_time(peer, buf, sizeof(buf), 0, &timeout); /* normally times out and quits */ timeout_quit = 0; if (n >= 4 && /* if read some data */ dp_opcode == DATA && /* and got a data block */ block == dp_block) { /* then my last ack was lost */ (void)send(peer, ackbuf, 4, 0); /* resend final ack */ } abort: return; } static const char *const errmsgs[] = { "Undefined error code", /* 0 - EUNDEF */ "File not found", /* 1 - ENOTFOUND */ "Access denied", /* 2 - EACCESS */ "Disk full or allocation exceeded", /* 3 - ENOSPACE */ "Illegal TFTP operation", /* 4 - EBADOP */ "Unknown transfer ID", /* 5 - EBADID */ "File already exists", /* 6 - EEXISTS */ "No such user", /* 7 - ENOUSER */ "Failure to negotiate RFC2347 options" /* 8 - EOPTNEG */ }; #define ERR_CNT (sizeof(errmsgs)/sizeof(const char *)) /* * Send a nak packet (error message). * Error code passed in is one of the * standard TFTP codes, or a UNIX errno * offset by 100. */ static void nak(int error, const char *msg) { struct tftphdr *tp; int length; tp = (struct tftphdr *)buf; tp->th_opcode = htons((u_short) ERROR); if (error >= 100) { /* This is a Unix errno+100 */ if (!msg) msg = strerror(error - 100); error = EUNDEF; } else { if ((unsigned)error >= ERR_CNT) error = EUNDEF; if (!msg) msg = errmsgs[error]; } tp->th_code = htons((u_short) error); length = strlen(msg) + 1; memcpy(tp->th_msg, msg, length); length += 4; /* Add space for header */ if (verbosity >= 2) { tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from), tmpbuf, INET6_ADDRSTRLEN); if (!tmp_p) { tmp_p = tmpbuf; strcpy(tmpbuf, "???"); } syslog(LOG_INFO, "sending NAK (%d, %s) to %s", error, tp->th_msg, tmp_p); } if (send(peer, buf, length, 0) != length) syslog(LOG_WARNING, "nak: %m"); }