/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T * All Rights Reserved. */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California. * All Rights Reserved. * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Trivial file transfer protocol server. A top level process runs in * an infinite loop fielding new TFTP requests. A child process, * communicating via a pipe with the top level process, sends delayed * NAKs for those that we can't handle. A new child process is created * to service each request that we can handle. The top level process * exits after a period of time during which no new requests are * received. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tftpcommon.h" #define TIMEOUT 5 #define DELAY_SECS 3 #define DALLYSECS 60 #define SYSLOG_MSG(message) \ (syslog((((errno == ENETUNREACH) || (errno == EHOSTUNREACH) || \ (errno == ECONNREFUSED)) ? LOG_WARNING : LOG_ERR), message)) static int rexmtval = TIMEOUT; static int maxtimeout = 5*TIMEOUT; static int securetftp; static int debug; static int disable_pnp; static int standalone; static uid_t uid_nobody = UID_NOBODY; static uid_t gid_nobody = GID_NOBODY; static int reqsock = -1; /* file descriptor of request socket */ static socklen_t fromlen; static socklen_t fromplen; static struct sockaddr_storage client; static struct sockaddr_in6 *sin6_ptr; static struct sockaddr_in *sin_ptr; static struct sockaddr_in6 *from6_ptr; static struct sockaddr_in *from_ptr; static int addrfmly; static int peer; static off_t tsize; static tftpbuf ackbuf; static struct sockaddr_storage from; static boolean_t tsize_set; static pid_t child; /* pid of child handling delayed replys */ static int delay_fd [2]; /* pipe for communicating with child */ static FILE *file; static char *filename; static union { struct tftphdr hdr; char data[SEGSIZE + 4]; } buf; static union { struct tftphdr hdr; char data[SEGSIZE]; } oackbuf; struct delay_info { long timestamp; /* time request received */ int ecode; /* error code to return */ struct sockaddr_storage from; /* address of client */ }; int blocksize = SEGSIZE; /* Number of data bytes in a DATA packet */ /* * Default directory for unqualified names * Used by TFTP boot procedures */ static char *homedir = "/tftpboot"; struct formats { char *f_mode; int (*f_validate)(int); void (*f_send)(struct formats *, int); void (*f_recv)(struct formats *, int); int f_convert; }; static void delayed_responder(void); static void tftp(struct tftphdr *, int); static int validate_filename(int); static void tftpd_sendfile(struct formats *, int); static void tftpd_recvfile(struct formats *, int); static void nak(int); static char *blksize_handler(int, char *, int *); static char *timeout_handler(int, char *, int *); static char *tsize_handler(int, char *, int *); static struct formats formats[] = { { "netascii", validate_filename, tftpd_sendfile, tftpd_recvfile, 1 }, { "octet", validate_filename, tftpd_sendfile, tftpd_recvfile, 0 }, { NULL } }; struct options { char *opt_name; char *(*opt_handler)(int, char *, int *); }; static struct options options[] = { { "blksize", blksize_handler }, { "timeout", timeout_handler }, { "tsize", tsize_handler }, { NULL } }; static char optbuf[MAX_OPTVAL_LEN]; static int timeout; static sigjmp_buf timeoutbuf; int main(int argc, char **argv) { struct tftphdr *tp; int n; int c; struct passwd *pwd; /* for "nobody" entry */ struct in_addr ipv4addr; char abuf[INET6_ADDRSTRLEN]; socklen_t addrlen; openlog("tftpd", LOG_PID, LOG_DAEMON); pwd = getpwnam("nobody"); if (pwd != NULL) { uid_nobody = pwd->pw_uid; gid_nobody = pwd->pw_gid; } (void) __init_daemon_priv( PU_LIMITPRIVS, uid_nobody, gid_nobody, PRIV_PROC_FORK, PRIV_PROC_CHROOT, NULL); /* * Limit set is still "all." Trim it down to just what we need: * fork and chroot. */ (void) priv_set(PRIV_SET, PRIV_ALLSETS, PRIV_PROC_FORK, PRIV_PROC_CHROOT, NULL); (void) priv_set(PRIV_SET, PRIV_EFFECTIVE, NULL); (void) priv_set(PRIV_SET, PRIV_INHERITABLE, NULL); while ((c = getopt(argc, argv, "dspS")) != EOF) switch (c) { case 'd': /* enable debug */ debug++; continue; case 's': /* secure daemon */ securetftp = 1; continue; case 'p': /* disable name pnp mapping */ disable_pnp = 1; continue; case 'S': standalone = 1; continue; case '?': default: usage: (void) fprintf(stderr, "usage: %s [-spd] [home-directory]\n", argv[0]); for (; optind < argc; optind++) syslog(LOG_ERR, "bad argument %s", argv[optind]); exit(1); } if (optind < argc) if (optind == argc - 1 && *argv [optind] == '/') homedir = argv [optind]; else goto usage; if (pipe(delay_fd) < 0) { syslog(LOG_ERR, "pipe (main): %m"); exit(1); } (void) sigset(SIGCHLD, SIG_IGN); /* no zombies please */ if (standalone) { socklen_t clientlen; sin6_ptr = (struct sockaddr_in6 *)&client; clientlen = sizeof (struct sockaddr_in6); reqsock = socket(AF_INET6, SOCK_DGRAM, 0); if (reqsock == -1) { perror("socket"); exit(1); } (void) memset(&client, 0, clientlen); sin6_ptr->sin6_family = AF_INET6; sin6_ptr->sin6_port = htons(IPPORT_TFTP); if (bind(reqsock, (struct sockaddr *)&client, clientlen) == -1) { perror("bind"); exit(1); } if (debug) (void) puts("running in standalone mode..."); } else { /* request socket passed on fd 0 by inetd */ reqsock = 0; } if (debug) { int on = 1; (void) setsockopt(reqsock, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof (on)); } (void) chdir(homedir); (void) priv_set(PRIV_SET, PRIV_EFFECTIVE, PRIV_PROC_FORK, NULL); if ((child = fork()) < 0) { syslog(LOG_ERR, "fork (main): %m"); exit(1); } (void) priv_set(PRIV_SET, PRIV_EFFECTIVE, NULL); if (child == 0) { (void) priv_set(PRIV_SET, PRIV_ALLSETS, NULL); delayed_responder(); } /* child */ /* close read side of pipe */ (void) close(delay_fd[0]); /* * Top level handling of incomming tftp requests. Read a request * and pass it off to be handled. If request is valid, handling * forks off and parent returns to this loop. If no new requests * are received for DALLYSECS, exit and return to inetd. */ for (;;) { fd_set readfds; struct timeval dally; FD_ZERO(&readfds); FD_SET(reqsock, &readfds); dally.tv_sec = DALLYSECS; dally.tv_usec = 0; n = select(reqsock + 1, &readfds, NULL, NULL, &dally); if (n < 0) { if (errno == EINTR) continue; syslog(LOG_ERR, "select: %m"); (void) kill(child, SIGKILL); exit(1); } if (n == 0) { /* Select timed out. Its time to die. */ if (standalone) continue; else { (void) kill(child, SIGKILL); exit(0); } } addrlen = sizeof (from); if (getsockname(reqsock, (struct sockaddr *)&from, &addrlen) < 0) { syslog(LOG_ERR, "getsockname: %m"); exit(1); } switch (from.ss_family) { case AF_INET: fromlen = (socklen_t)sizeof (struct sockaddr_in); break; case AF_INET6: fromlen = (socklen_t)sizeof (struct sockaddr_in6); break; default: syslog(LOG_ERR, "Unknown address Family on peer connection %d", from.ss_family); exit(1); } n = recvfrom(reqsock, &buf, sizeof (buf), 0, (struct sockaddr *)&from, &fromlen); if (n < 0) { if (errno == EINTR) continue; if (standalone) perror("recvfrom"); else syslog(LOG_ERR, "recvfrom: %m"); (void) kill(child, SIGKILL); exit(1); } (void) alarm(0); switch (from.ss_family) { case AF_INET: addrfmly = AF_INET; fromplen = sizeof (struct sockaddr_in); sin_ptr = (struct sockaddr_in *)&client; (void) memset(&client, 0, fromplen); sin_ptr->sin_family = AF_INET; break; case AF_INET6: addrfmly = AF_INET6; fromplen = sizeof (struct sockaddr_in6); sin6_ptr = (struct sockaddr_in6 *)&client; (void) memset(&client, 0, fromplen); sin6_ptr->sin6_family = AF_INET6; break; default: syslog(LOG_ERR, "Unknown address Family on peer connection"); exit(1); } peer = socket(addrfmly, SOCK_DGRAM, 0); if (peer < 0) { if (standalone) perror("socket (main)"); else syslog(LOG_ERR, "socket (main): %m"); (void) kill(child, SIGKILL); exit(1); } if (debug) { int on = 1; (void) setsockopt(peer, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof (on)); } if (bind(peer, (struct sockaddr *)&client, fromplen) < 0) { if (standalone) perror("bind (main)"); else syslog(LOG_ERR, "bind (main): %m"); (void) kill(child, SIGKILL); exit(1); } if (standalone && debug) { sin6_ptr = (struct sockaddr_in6 *)&client; from6_ptr = (struct sockaddr_in6 *)&from; if (IN6_IS_ADDR_V4MAPPED(&from6_ptr->sin6_addr)) { IN6_V4MAPPED_TO_INADDR(&from6_ptr->sin6_addr, &ipv4addr); (void) inet_ntop(AF_INET, &ipv4addr, abuf, sizeof (abuf)); } else { (void) inet_ntop(AF_INET6, &from6_ptr->sin6_addr, abuf, sizeof (abuf)); } /* get local port */ if (getsockname(peer, (struct sockaddr *)&client, &fromplen) < 0) perror("getsockname (main)"); (void) fprintf(stderr, "request from %s port %d; local port %d\n", abuf, from6_ptr->sin6_port, sin6_ptr->sin6_port); } tp = &buf.hdr; tp->th_opcode = ntohs((ushort_t)tp->th_opcode); if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) tftp(tp, n); (void) close(peer); (void) fclose(file); } /*NOTREACHED*/ return (0); } static void delayed_responder(void) { struct delay_info dinfo; long now; /* we don't use the descriptors passed in to the parent */ (void) close(0); (void) close(1); if (standalone) (void) close(reqsock); /* close write side of pipe */ (void) close(delay_fd[1]); for (;;) { int n; if ((n = read(delay_fd[0], &dinfo, sizeof (dinfo))) != sizeof (dinfo)) { if (n < 0) { if (errno == EINTR) continue; if (standalone) perror("read from pipe " "(delayed responder)"); else syslog(LOG_ERR, "read from pipe: %m"); } exit(1); } switch (dinfo.from.ss_family) { case AF_INET: addrfmly = AF_INET; fromplen = sizeof (struct sockaddr_in); sin_ptr = (struct sockaddr_in *)&client; (void) memset(&client, 0, fromplen); sin_ptr->sin_family = AF_INET; break; case AF_INET6: addrfmly = AF_INET6; fromplen = sizeof (struct sockaddr_in6); sin6_ptr = (struct sockaddr_in6 *)&client; (void) memset(&client, 0, fromplen); sin6_ptr->sin6_family = AF_INET6; break; } peer = socket(addrfmly, SOCK_DGRAM, 0); if (peer == -1) { if (standalone) perror("socket (delayed responder)"); else syslog(LOG_ERR, "socket (delay): %m"); exit(1); } if (debug) { int on = 1; (void) setsockopt(peer, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof (on)); } if (bind(peer, (struct sockaddr *)&client, fromplen) < 0) { if (standalone) perror("bind (delayed responder)"); else syslog(LOG_ERR, "bind (delay): %m"); exit(1); } if (client.ss_family == AF_INET) { from_ptr = (struct sockaddr_in *)&dinfo.from; from_ptr->sin_family = AF_INET; } else { from6_ptr = (struct sockaddr_in6 *)&dinfo.from; from6_ptr->sin6_family = AF_INET6; } /* * Since a request hasn't been received from the client * before the delayed responder process is forked, the * from variable is uninitialized. So set it to contain * the client address. */ from = dinfo.from; /* * only sleep if DELAY_SECS has not elapsed since * original request was received. Ensure that `now' * is not earlier than `dinfo.timestamp' */ now = time(0); if ((uint_t)(now - dinfo.timestamp) < DELAY_SECS) (void) sleep(DELAY_SECS - (now - dinfo.timestamp)); nak(dinfo.ecode); (void) close(peer); } /* for */ /* NOTREACHED */ } /* * Handle the Blocksize option. * Return the blksize option value string to include in the OACK reply. */ /*ARGSUSED*/ static char * blksize_handler(int opcode, char *optval, int *errcode) { char *endp; int value; *errcode = -1; errno = 0; value = (int)strtol(optval, &endp, 10); if (errno != 0 || value < MIN_BLKSIZE || *endp != '\0') return (NULL); /* * As the blksize value in the OACK reply can be less than the value * requested, to support broken clients if the value requested is larger * than allowed in the RFC, reply with the maximum value permitted. */ if (value > MAX_BLKSIZE) value = MAX_BLKSIZE; blocksize = value; (void) snprintf(optbuf, sizeof (optbuf), "%d", blocksize); return (optbuf); } /* * Handle the Timeout Interval option. * Return the timeout option value string to include in the OACK reply. */ /*ARGSUSED*/ static char * timeout_handler(int opcode, char *optval, int *errcode) { char *endp; int value; *errcode = -1; errno = 0; value = (int)strtol(optval, &endp, 10); if (errno != 0 || *endp != '\0') return (NULL); /* * The timeout value in the OACK reply must match the value specified * by the client, so if an invalid timeout is requested don't include * the timeout option in the OACK reply. */ if (value < MIN_TIMEOUT || value > MAX_TIMEOUT) return (NULL); rexmtval = value; maxtimeout = 5 * rexmtval; (void) snprintf(optbuf, sizeof (optbuf), "%d", rexmtval); return (optbuf); } /* * Handle the Transfer Size option. * Return the tsize option value string to include in the OACK reply. */ static char * tsize_handler(int opcode, char *optval, int *errcode) { char *endp; longlong_t value; *errcode = -1; errno = 0; value = strtoll(optval, &endp, 10); if (errno != 0 || value < 0 || *endp != '\0') return (NULL); if (opcode == RRQ) { if (tsize_set == B_FALSE) return (NULL); /* * The tsize value should be 0 for a read request, but to * support broken clients we don't check that it is. */ } else { #if _FILE_OFFSET_BITS == 32 if (value > MAXOFF_T) { *errcode = ENOSPACE; return (NULL); } #endif tsize = value; tsize_set = B_TRUE; } (void) snprintf(optbuf, sizeof (optbuf), OFF_T_FMT, tsize); return (optbuf); } /* * Process any options included by the client in the request packet. * Return the size of the OACK reply packet built or 0 for no OACK reply. */ static int process_options(int opcode, char *opts, char *endopts) { char *cp, *optname, *optval, *ostr, *oackend; struct tftphdr *oackp; int i, errcode; /* * To continue to interoperate with broken TFTP clients, ignore * null padding appended to requests which don't include options. */ cp = opts; while ((cp < endopts) && (*cp == '\0')) cp++; if (cp == endopts) return (0); /* * Construct an Option ACKnowledgement packet if any requested option * is recognized. */ oackp = &oackbuf.hdr; oackend = oackbuf.data + sizeof (oackbuf.data); oackp->th_opcode = htons((ushort_t)OACK); cp = (char *)&oackp->th_stuff; while (opts < endopts) { optname = opts; if ((optval = next_field(optname, endopts)) == NULL) { nak(EOPTNEG); exit(1); } if ((opts = next_field(optval, endopts)) == NULL) { nak(EOPTNEG); exit(1); } for (i = 0; options[i].opt_name != NULL; i++) { if (strcasecmp(optname, options[i].opt_name) == 0) break; } if (options[i].opt_name != NULL) { ostr = options[i].opt_handler(opcode, optval, &errcode); if (ostr != NULL) { cp += strlcpy(cp, options[i].opt_name, oackend - cp) + 1; if (cp <= oackend) cp += strlcpy(cp, ostr, oackend - cp) + 1; if (cp > oackend) { nak(EOPTNEG); exit(1); } } else if (errcode >= 0) { nak(errcode); exit(1); } } } if (cp != (char *)&oackp->th_stuff) return (cp - oackbuf.data); return (0); } /* * Handle access errors caused by client requests. */ static void delay_exit(int ecode) { struct delay_info dinfo; /* * The most likely cause of an error here is that * someone has broadcast an RRQ packet because s/he's * trying to boot and doesn't know who the server is. * Rather then sending an ERROR packet immediately, we * wait a while so that the real server has a better chance * of getting through (in case client has lousy Ethernet * interface). We write to a child that handles delayed * ERROR packets to avoid delaying service to new * requests. Of course, we would rather just not answer * RRQ packets that are broadcasted, but there's no way * for a user process to determine this. */ dinfo.timestamp = time(0); /* * If running in secure mode, we map all errors to EACCESS * so that the client gets no information about which files * or directories exist. */ if (securetftp) dinfo.ecode = EACCESS; else dinfo.ecode = ecode; dinfo.from = from; if (write(delay_fd[1], &dinfo, sizeof (dinfo)) != sizeof (dinfo)) { syslog(LOG_ERR, "delayed write failed."); (void) kill(child, SIGKILL); exit(1); } exit(0); } /* * Handle initial connection protocol. */ static void tftp(struct tftphdr *tp, int size) { char *cp; int readmode, ecode; struct formats *pf; char *mode; int fd; static boolean_t firsttime = B_TRUE; int oacklen; struct stat statb; readmode = (tp->th_opcode == RRQ); filename = (char *)&tp->th_stuff; mode = next_field(filename, &buf.data[size]); cp = (mode != NULL) ? next_field(mode, &buf.data[size]) : NULL; if (cp == NULL) { nak(EBADOP); exit(1); } if (debug && standalone) { (void) fprintf(stderr, "%s for %s %s ", readmode ? "RRQ" : "WRQ", filename, mode); print_options(stderr, cp, size + buf.data - cp); (void) putc('\n', stderr); } for (pf = formats; pf->f_mode != NULL; pf++) if (strcasecmp(pf->f_mode, mode) == 0) break; if (pf->f_mode == NULL) { nak(EBADOP); exit(1); } /* * XXX fork a new process to handle this request before * chroot(), otherwise the parent won't be able to create a * new socket as that requires library access to system files * and devices. */ (void) priv_set(PRIV_SET, PRIV_EFFECTIVE, PRIV_PROC_FORK, NULL); switch (fork()) { case -1: syslog(LOG_ERR, "fork (tftp): %m"); (void) priv_set(PRIV_SET, PRIV_EFFECTIVE, NULL); return; case 0: (void) priv_set(PRIV_SET, PRIV_EFFECTIVE, NULL); break; default: (void) priv_set(PRIV_SET, PRIV_EFFECTIVE, NULL); return; } /* * Try to see if we can access the file. The access can still * fail later if we are running in secure mode because of * the chroot() call. We only want to execute the chroot() once. */ if (securetftp && firsttime) { (void) priv_set( PRIV_SET, PRIV_EFFECTIVE, PRIV_PROC_CHROOT, NULL); if (chroot(homedir) == -1) { syslog(LOG_ERR, "tftpd: cannot chroot to directory %s: %m\n", homedir); delay_exit(EACCESS); } else { firsttime = B_FALSE; } (void) priv_set(PRIV_SET, PRIV_EFFECTIVE, NULL); (void) chdir("/"); /* cd to new root */ } (void) priv_set(PRIV_SET, PRIV_ALLSETS, NULL); ecode = (*pf->f_validate)(tp->th_opcode); if (ecode != 0) delay_exit(ecode); /* we don't use the descriptors passed in to the parent */ (void) close(STDIN_FILENO); (void) close(STDOUT_FILENO); /* * Try to open file as low-priv setuid/setgid. Note that * a chroot() has already been done. */ fd = open(filename, (readmode ? O_RDONLY : (O_WRONLY|O_TRUNC)) | O_NONBLOCK); if ((fd < 0) || (fstat(fd, &statb) < 0)) delay_exit((errno == ENOENT) ? ENOTFOUND : EACCESS); if (((statb.st_mode & ((readmode) ? S_IROTH : S_IWOTH)) == 0) || ((statb.st_mode & S_IFMT) != S_IFREG)) delay_exit(EACCESS); file = fdopen(fd, readmode ? "r" : "w"); if (file == NULL) delay_exit(errno + 100); /* Don't know the size of transfers which involve conversion */ tsize_set = (readmode && (pf->f_convert == 0)); if (tsize_set) tsize = statb.st_size; /* Deal with any options sent by the client */ oacklen = process_options(tp->th_opcode, cp, buf.data + size); if (tp->th_opcode == WRQ) (*pf->f_recv)(pf, oacklen); else (*pf->f_send)(pf, oacklen); exit(0); } /* * Maybe map filename into another one. * * For PNP, we get TFTP boot requests for filenames like * .. We must * map these to 'pnp.'. Note that * uppercase is mapped to lowercase in the architecture names. * * For names there are two cases. First, * it may be a buggy prom that omits the architecture code. * So first check if . is on the filesystem. * Second, this is how most Sun3s work; assume is sun3. */ static char * pnp_check(char *origname) { static char buf [MAXNAMLEN + 1]; char *arch, *s, *bufend; in_addr_t ipaddr; int len = (origname ? strlen(origname) : 0); DIR *dir; struct dirent *dp; if (securetftp || disable_pnp || len < 8 || len > 14) return (NULL); /* * XXX see if this cable allows pnp; if not, return NULL * Requires YP support for determining this! */ ipaddr = htonl(strtol(origname, &arch, 16)); if ((arch == NULL) || (len > 8 && *arch != '.')) return (NULL); if (len == 8) arch = "SUN3"; else arch++; /* * Allow * filename request to to be * satisfied by rather * than enforcing this to be Sun3 systems. Also serves * to make case of suffix a don't-care. */ if ((dir = opendir(homedir)) == NULL) return (NULL); while ((dp = readdir(dir)) != NULL) { if (strncmp(origname, dp->d_name, 8) == 0) { (void) strlcpy(buf, dp->d_name, sizeof (buf)); (void) closedir(dir); return (buf); } } (void) closedir(dir); /* * XXX maybe call YP master for most current data iff * pnp is enabled. */ /* * only do mapping PNP boot file name for machines that * are not in the hosts database. */ if (gethostbyaddr((char *)&ipaddr, sizeof (ipaddr), AF_INET) != NULL) return (NULL); s = buf + strlcpy(buf, "pnp.", sizeof (buf)); bufend = &buf[sizeof (buf) - 1]; while ((*arch != '\0') && (s < bufend)) *s++ = tolower (*arch++); *s = '\0'; return (buf); } /* * Try to validate filename. If the filename doesn't exist try PNP mapping. */ static int validate_filename(int mode) { struct stat stbuf; char *origfile; if (stat(filename, &stbuf) < 0) { if (errno != ENOENT) return (EACCESS); if (mode == WRQ) return (ENOTFOUND); /* try to map requested filename into a pnp filename */ origfile = filename; filename = pnp_check(origfile); if (filename == NULL) return (ENOTFOUND); if (stat(filename, &stbuf) < 0) return (errno == ENOENT ? ENOTFOUND : EACCESS); syslog(LOG_NOTICE, "%s -> %s\n", origfile, filename); } return (0); } /* ARGSUSED */ static void timer(int signum) { timeout += rexmtval; if (timeout >= maxtimeout) exit(1); siglongjmp(timeoutbuf, 1); } /* * Send the requested file. */ static void tftpd_sendfile(struct formats *pf, int oacklen) { struct tftphdr *dp; volatile ushort_t block = 1; int size, n, serrno; if (oacklen != 0) { (void) sigset(SIGALRM, timer); timeout = 0; (void) sigsetjmp(timeoutbuf, 1); if (debug && standalone) { (void) fputs("Sending OACK ", stderr); print_options(stderr, (char *)&oackbuf.hdr.th_stuff, oacklen - 2); (void) putc('\n', stderr); } if (sendto(peer, &oackbuf, oacklen, 0, (struct sockaddr *)&from, fromplen) != oacklen) { if (debug && standalone) { serrno = errno; perror("sendto (oack)"); errno = serrno; } SYSLOG_MSG("sendto (oack): %m"); goto abort; } (void) alarm(rexmtval); /* read the ack */ for (;;) { (void) sigrelse(SIGALRM); n = recv(peer, &ackbuf, sizeof (ackbuf), 0); (void) sighold(SIGALRM); if (n < 0) { if (errno == EINTR) continue; serrno = errno; SYSLOG_MSG("recv (ack): %m"); if (debug && standalone) { errno = serrno; perror("recv (ack)"); } goto abort; } ackbuf.tb_hdr.th_opcode = ntohs((ushort_t)ackbuf.tb_hdr.th_opcode); ackbuf.tb_hdr.th_block = ntohs((ushort_t)ackbuf.tb_hdr.th_block); if (ackbuf.tb_hdr.th_opcode == ERROR) { if (debug && standalone) { (void) fprintf(stderr, "received ERROR %d", ackbuf.tb_hdr.th_code); if (n > 4) (void) fprintf(stderr, " %.*s", n - 4, ackbuf.tb_hdr.th_msg); (void) putc('\n', stderr); } goto abort; } if (ackbuf.tb_hdr.th_opcode == ACK) { if (debug && standalone) (void) fprintf(stderr, "received ACK for block %d\n", ackbuf.tb_hdr.th_block); if (ackbuf.tb_hdr.th_block == 0) break; /* * Don't resend the OACK, avoids getting stuck * in an OACK/ACK loop if the client keeps * replying with a bad ACK. Client will either * send a good ACK or timeout sending bad ones. */ } } cancel_alarm(); } dp = r_init(); do { (void) sigset(SIGALRM, timer); size = readit(file, &dp, pf->f_convert); if (size < 0) { nak(errno + 100); goto abort; } dp->th_opcode = htons((ushort_t)DATA); dp->th_block = htons((ushort_t)block); timeout = 0; (void) sigsetjmp(timeoutbuf, 1); if (debug && standalone) (void) fprintf(stderr, "Sending DATA block %d\n", block); if (sendto(peer, dp, size + 4, 0, (struct sockaddr *)&from, fromplen) != size + 4) { if (debug && standalone) { serrno = errno; perror("sendto (data)"); errno = serrno; } SYSLOG_MSG("sendto (data): %m"); goto abort; } read_ahead(file, pf->f_convert); (void) alarm(rexmtval); /* read the ack */ for (;;) { (void) sigrelse(SIGALRM); n = recv(peer, &ackbuf, sizeof (ackbuf), 0); (void) sighold(SIGALRM); if (n < 0) { if (errno == EINTR) continue; serrno = errno; SYSLOG_MSG("recv (ack): %m"); if (debug && standalone) { errno = serrno; perror("recv (ack)"); } goto abort; } ackbuf.tb_hdr.th_opcode = ntohs((ushort_t)ackbuf.tb_hdr.th_opcode); ackbuf.tb_hdr.th_block = ntohs((ushort_t)ackbuf.tb_hdr.th_block); if (ackbuf.tb_hdr.th_opcode == ERROR) { if (debug && standalone) { (void) fprintf(stderr, "received ERROR %d", ackbuf.tb_hdr.th_code); if (n > 4) (void) fprintf(stderr, " %.*s", n - 4, ackbuf.tb_hdr.th_msg); (void) putc('\n', stderr); } goto abort; } if (ackbuf.tb_hdr.th_opcode == ACK) { if (debug && standalone) (void) fprintf(stderr, "received ACK for block %d\n", ackbuf.tb_hdr.th_block); if (ackbuf.tb_hdr.th_block == block) { break; } /* * Never resend the current DATA packet on * receipt of a duplicate ACK, doing so would * cause the "Sorcerer's Apprentice Syndrome". */ } } cancel_alarm(); block++; } while (size == blocksize); abort: cancel_alarm(); (void) fclose(file); } /* ARGSUSED */ static void justquit(int signum) { exit(0); } /* * Receive a file. */ static void tftpd_recvfile(struct formats *pf, int oacklen) { struct tftphdr *dp; struct tftphdr *ap; /* ack buffer */ ushort_t block = 0; int n, size, acklen, serrno; dp = w_init(); ap = &ackbuf.tb_hdr; do { (void) sigset(SIGALRM, timer); timeout = 0; if (oacklen == 0) { ap->th_opcode = htons((ushort_t)ACK); ap->th_block = htons((ushort_t)block); acklen = 4; } else { /* copy OACK packet to the ack buffer ready to send */ (void) memcpy(&ackbuf, &oackbuf, oacklen); acklen = oacklen; oacklen = 0; } block++; (void) sigsetjmp(timeoutbuf, 1); send_ack: if (debug && standalone) { if (ap->th_opcode == htons((ushort_t)ACK)) { (void) fprintf(stderr, "Sending ACK for block %d\n", block - 1); } else { (void) fprintf(stderr, "Sending OACK "); print_options(stderr, (char *)&ap->th_stuff, acklen - 2); (void) putc('\n', stderr); } } if (sendto(peer, &ackbuf, acklen, 0, (struct sockaddr *)&from, fromplen) != acklen) { if (ap->th_opcode == htons((ushort_t)ACK)) { if (debug && standalone) { serrno = errno; perror("sendto (ack)"); errno = serrno; } syslog(LOG_ERR, "sendto (ack): %m\n"); } else { if (debug && standalone) { serrno = errno; perror("sendto (oack)"); errno = serrno; } syslog(LOG_ERR, "sendto (oack): %m\n"); } goto abort; } if (write_behind(file, pf->f_convert) < 0) { nak(errno + 100); goto abort; } (void) alarm(rexmtval); for (;;) { (void) sigrelse(SIGALRM); n = recv(peer, dp, blocksize + 4, 0); (void) sighold(SIGALRM); if (n < 0) { /* really? */ if (errno == EINTR) continue; syslog(LOG_ERR, "recv (data): %m"); goto abort; } dp->th_opcode = ntohs((ushort_t)dp->th_opcode); dp->th_block = ntohs((ushort_t)dp->th_block); if (dp->th_opcode == ERROR) { cancel_alarm(); if (debug && standalone) { (void) fprintf(stderr, "received ERROR %d", dp->th_code); if (n > 4) (void) fprintf(stderr, " %.*s", n - 4, dp->th_msg); (void) putc('\n', stderr); } return; } if (dp->th_opcode == DATA) { if (debug && standalone) (void) fprintf(stderr, "Received DATA block %d\n", dp->th_block); if (dp->th_block == block) { break; /* normal */ } /* Re-synchronize with the other side */ if (synchnet(peer) < 0) { nak(errno + 100); goto abort; } if (dp->th_block == (block-1)) goto send_ack; /* rexmit */ } } cancel_alarm(); /* size = write(file, dp->th_data, n - 4); */ size = writeit(file, &dp, n - 4, pf->f_convert); if (size != (n - 4)) { nak((size < 0) ? (errno + 100) : ENOSPACE); goto abort; } } while (size == blocksize); if (write_behind(file, pf->f_convert) < 0) { nak(errno + 100); goto abort; } n = fclose(file); /* close data file */ file = NULL; if (n == EOF) { nak(errno + 100); goto abort; } ap->th_opcode = htons((ushort_t)ACK); /* send the "final" ack */ ap->th_block = htons((ushort_t)(block)); if (debug && standalone) (void) fprintf(stderr, "Sending ACK for block %d\n", block); if (sendto(peer, &ackbuf, 4, 0, (struct sockaddr *)&from, fromplen) == -1) { if (debug && standalone) perror("sendto (ack)"); } (void) sigset(SIGALRM, justquit); /* just quit on timeout */ (void) alarm(rexmtval); /* normally times out and quits */ n = recv(peer, dp, blocksize + 4, 0); (void) alarm(0); dp->th_opcode = ntohs((ushort_t)dp->th_opcode); dp->th_block = ntohs((ushort_t)dp->th_block); if (n >= 4 && /* if read some data */ dp->th_opcode == DATA && /* and got a data block */ block == dp->th_block) { /* then my last ack was lost */ if (debug && standalone) { (void) fprintf(stderr, "Sending ACK for block %d\n", block); } /* resend final ack */ if (sendto(peer, &ackbuf, 4, 0, (struct sockaddr *)&from, fromplen) == -1) { if (debug && standalone) perror("sendto (last ack)"); } } abort: cancel_alarm(); if (file != NULL) (void) fclose(file); } /* * Send a nak packet (error message). * Error code passed in is one of the * standard TFTP codes, or a UNIX errno * offset by 100. * Handles connected as well as unconnected peer. */ static void nak(int error) { struct tftphdr *tp; int length; struct errmsg *pe; int ret; tp = &buf.hdr; tp->th_opcode = htons((ushort_t)ERROR); tp->th_code = htons((ushort_t)error); for (pe = errmsgs; pe->e_code >= 0; pe++) if (pe->e_code == error) break; if (pe->e_code < 0) { pe->e_msg = strerror(error - 100); tp->th_code = EUNDEF; /* set 'undef' errorcode */ } (void) strlcpy(tp->th_msg, (pe->e_msg != NULL) ? pe->e_msg : "UNKNOWN", sizeof (buf) - sizeof (struct tftphdr)); length = strlen(tp->th_msg); length += sizeof (struct tftphdr); if (debug && standalone) (void) fprintf(stderr, "Sending NAK: %s\n", tp->th_msg); ret = sendto(peer, &buf, length, 0, (struct sockaddr *)&from, fromplen); if (ret == -1 && errno == EISCONN) { /* Try without an address */ ret = send(peer, &buf, length, 0); } if (ret == -1) { if (standalone) perror("sendto (nak)"); else syslog(LOG_ERR, "tftpd: nak: %m\n"); } else if (ret != length) { if (standalone) perror("sendto (nak) lost data"); else syslog(LOG_ERR, "tftpd: nak: %d lost\n", length - ret); } }