in.tftpd.c revision 07ea95b60d67b304d76a5e8bc17580c913db97ac
/*
* 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
* 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 2007 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 <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 <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) \
static int securetftp;
static int debug;
static int disable_pnp;
static int standalone;
static int reqsock = -1;
/* file descriptor of request socket */
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 struct sockaddr_storage from;
/* pid of child handling delayed replys */
static int delay_fd [2];
/* pipe for communicating with child */
static char *filename;
static union {
} buf;
static union {
} oackbuf;
struct delay_info {
long timestamp; /* time request received */
int ecode; /* error code to return */
};
/*
* Default directory for unqualified names
* Used by TFTP boot procedures
*/
static char *homedir = "/tftpboot";
struct formats {
char *f_mode;
int (*f_validate)(int);
int f_convert;
};
static void delayed_responder(void);
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 *);
{ NULL }
};
struct options {
char *opt_name;
char *(*opt_handler)(int, char *, int *);
};
{ "blksize", blksize_handler },
{ "timeout", timeout_handler },
{ "tsize", tsize_handler },
{ NULL }
};
static char optbuf[MAX_OPTVAL_LEN];
static int timeout;
static sigjmp_buf timeoutbuf;
int
{
int n;
int c;
char abuf[INET6_ADDRSTRLEN];
}
(void) __init_daemon_priv(
/*
* Limit set is still "all." Trim it down to just what we need:
* fork and chroot.
*/
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: %s [-spd] [home-directory]\n", argv[0]);
exit(1);
}
else
goto usage;
exit(1);
}
if (standalone) {
clientlen = sizeof (struct sockaddr_in6);
if (reqsock == -1) {
perror("socket");
exit(1);
}
/* Enable privilege as tftp port is < 1024 */
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;
}
exit(1);
}
if (child == 0) {
} /* child */
/* close read side of pipe */
/*
* 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 (;;) {
if (n < 0) {
continue;
exit(1);
}
if (n == 0) {
/* Select timed out. Its time to die. */
if (standalone)
continue;
else {
exit(0);
}
}
&addrlen) < 0) {
exit(1);
}
case AF_INET:
break;
case AF_INET6:
break;
default:
"Unknown address Family on peer connection %d",
exit(1);
}
if (n < 0) {
continue;
if (standalone)
perror("recvfrom");
else
exit(1);
}
(void) alarm(0);
case AF_INET:
fromplen = sizeof (struct sockaddr_in);
break;
case AF_INET6:
fromplen = sizeof (struct sockaddr_in6);
break;
default:
"Unknown address Family on peer connection");
exit(1);
}
if (peer < 0) {
if (standalone)
perror("socket (main)");
else
exit(1);
}
if (debug) {
int on = 1;
}
if (standalone)
perror("bind (main)");
else
exit(1);
}
if (standalone && debug) {
&ipv4addr);
sizeof (abuf));
} else {
sizeof (abuf));
}
/* get local port */
&fromplen) < 0)
perror("getsockname (main)");
"request from %s port %d; local port %d\n",
}
}
/*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)
/* close write side of pipe */
for (;;) {
int n;
if (n < 0) {
continue;
if (standalone)
perror("read from pipe "
"(delayed responder)");
else
}
exit(1);
}
case AF_INET:
fromplen = sizeof (struct sockaddr_in);
break;
case AF_INET6:
fromplen = sizeof (struct sockaddr_in6);
break;
}
if (peer == -1) {
if (standalone)
perror("socket (delayed responder)");
else
exit(1);
}
if (debug) {
int on = 1;
}
if (standalone)
perror("bind (delayed responder)");
else
exit(1);
}
} else {
}
/*
* 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.
*/
/*
* only sleep if DELAY_SECS has not elapsed since
* original request was received. Ensure that `now'
* is not earlier than `dinfo.timestamp'
*/
} /* for */
/* NOTREACHED */
}
/*
* Handle the Blocksize option.
* Return the blksize option value string to include in the OACK reply.
*/
/*ARGSUSED*/
static char *
{
char *endp;
int value;
*errcode = -1;
errno = 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;
return (optbuf);
}
/*
* Handle the Timeout Interval option.
* Return the timeout option value string to include in the OACK reply.
*/
/*ARGSUSED*/
static char *
{
char *endp;
int value;
*errcode = -1;
errno = 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.
*/
return (NULL);
return (optbuf);
}
/*
* Handle the Transfer Size option.
* Return the tsize option value string to include in the OACK reply.
*/
static char *
{
char *endp;
*errcode = -1;
errno = 0;
return (NULL);
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
return (NULL);
}
#endif
}
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
{
int i, errcode;
/*
* To continue to interoperate with broken TFTP clients, ignore
* null padding appended to requests which don't include options.
*/
cp++;
return (0);
/*
* Construct an Option ACKnowledgement packet if any requested option
* is recognized.
*/
exit(1);
}
exit(1);
}
break;
}
+ 1;
exit(1);
}
} else if (errcode >= 0) {
exit(1);
}
}
}
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
* 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.
*/
/*
* 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)
else
sizeof (dinfo)) {
exit(1);
}
exit(0);
}
/*
* Handle initial connection protocol.
*/
static void
{
char *cp;
char *mode;
int fd;
int oacklen;
exit(1);
}
if (debug && standalone) {
}
break;
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.
*/
switch (fork()) {
case -1:
return;
case 0:
break;
default:
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(
"tftpd: cannot chroot to directory %s: %m\n",
homedir);
}
else
{
}
}
if (ecode != 0)
/* we don't use the descriptors passed in to the parent */
(void) close(STDIN_FILENO);
(void) close(STDOUT_FILENO);
/*
* a chroot() has already been done.
*/
/* Don't know the size of transfers which involve conversion */
if (tsize_set)
/* Deal with any options sent by the client */
else
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 *
{
return (NULL);
/*
* XXX see if this cable allows pnp; if not, return NULL
* Requires YP support for determining this!
*/
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.
*/
return (NULL);
return (buf);
}
}
/*
* 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.
*/
return (NULL);
*s = '\0';
return (buf);
}
/*
* Try to validate filename. If the filename doesn't exist try PNP mapping.
*/
static int
validate_filename(int mode)
{
char *origfile;
return (EACCESS);
return (ENOTFOUND);
/* try to map requested filename into a pnp filename */
return (ENOTFOUND);
}
return (0);
}
/* ARGSUSED */
static void
{
if (timeout >= maxtimeout)
exit(1);
}
/*
* Send the requested file.
*/
static void
{
if (oacklen != 0) {
timeout = 0;
if (debug && standalone) {
oacklen - 2);
}
if (debug && standalone) {
perror("sendto (oack)");
}
SYSLOG_MSG("sendto (oack): %m");
goto abort;
}
for (;;) {
if (n < 0) {
continue;
SYSLOG_MSG("recv (ack): %m");
if (debug && standalone) {
perror("recv (ack)");
}
goto abort;
}
if (debug && standalone) {
"received ERROR %d",
if (n > 4)
" %.*s", n - 4,
}
goto abort;
}
if (debug && standalone)
"received ACK for block %d\n",
break;
/*
* Don't resend the OACK, avoids getting stuck
* replying with a bad ACK. Client will either
* send a good ACK or timeout sending bad ones.
*/
}
}
cancel_alarm();
}
do {
if (size < 0) {
goto abort;
}
timeout = 0;
if (debug && standalone)
block);
if (debug && standalone) {
perror("sendto (data)");
}
SYSLOG_MSG("sendto (data): %m");
goto abort;
}
for (;;) {
if (n < 0) {
continue;
SYSLOG_MSG("recv (ack): %m");
if (debug && standalone) {
perror("recv (ack)");
}
goto abort;
}
if (debug && standalone) {
"received ERROR %d",
if (n > 4)
" %.*s", n - 4,
}
goto abort;
}
if (debug && standalone)
"received ACK for block %d\n",
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++;
cancel_alarm();
}
/* ARGSUSED */
static void
{
exit(0);
}
/*
* Receive a file.
*/
static void
{
do {
timeout = 0;
if (oacklen == 0) {
acklen = 4;
} else {
/* copy OACK packet to the ack buffer ready to send */
oacklen = 0;
}
block++;
if (debug && standalone) {
} else {
acklen - 2);
}
}
if (debug && standalone) {
perror("sendto (ack)");
}
} else {
if (debug && standalone) {
perror("sendto (oack)");
}
}
goto abort;
}
goto abort;
}
for (;;) {
if (n < 0) { /* really? */
continue;
goto abort;
}
cancel_alarm();
if (debug && standalone) {
if (n > 4)
}
return;
}
if (debug && standalone)
"Received DATA block %d\n",
break; /* normal */
}
/* Re-synchronize with the other side */
goto abort;
}
goto send_ack; /* rexmit */
}
}
cancel_alarm();
/* size = write(file, dp->th_data, n - 4); */
if (size != (n - 4)) {
goto abort;
}
goto abort;
}
if (n == EOF) {
goto abort;
}
if (debug && standalone)
fromplen) == -1) {
if (debug && standalone)
perror("sendto (ack)");
}
/* normally times out and quits */
(void) alarm(0);
if (n >= 4 && /* if read some data */
if (debug && standalone) {
block);
}
/* resend final ack */
fromplen) == -1) {
if (debug && standalone)
perror("sendto (last ack)");
}
}
cancel_alarm();
}
/*
* 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
{
int length;
int ret;
break;
}
if (debug && standalone)
fromplen);
/* Try without an address */
}
if (ret == -1) {
if (standalone)
perror("sendto (nak)");
else
if (standalone)
perror("sendto (nak) lost data");
else
}
}