--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.sbin/in.tftpd.c Tue Jun 14 00:00:00 2005 -0700
@@ -0,0 +1,1376 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (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 2004 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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <setjmp.h>
+#include <syslog.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <string.h>
+#include <priv_utils.h>
+#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
+ * <Unknown Hex IP Addr>.<Architecture Name>. We must
+ * map these to 'pnp.<Architecture Name>'. Note that
+ * uppercase is mapped to lowercase in the architecture names.
+ *
+ * For names <Hex IP Addr> there are two cases. First,
+ * it may be a buggy prom that omits the architecture code.
+ * So first check if <Hex IP Addr>.<arch> is on the filesystem.
+ * Second, this is how most Sun3s work; assume <arch> 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 <Hex IP Addr>* filename request to to be
+ * satisfied by <Hex IP Addr><Any Suffix> 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 int 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 */
+ int block = 0, 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);
+ }
+}