WARNS?=1. merge lite-2.

This commit is contained in:
mrg 1997-10-07 09:46:45 +00:00
parent 7bcd5087e9
commit 60cef5019e
3 changed files with 274 additions and 114 deletions

View File

@ -1,9 +1,10 @@
# from: @(#)Makefile 5.10 (Berkeley) 5/11/90
# $Id: Makefile,v 1.3 1994/12/22 10:28:22 cgd Exp $
# $NetBSD: Makefile,v 1.4 1997/10/07 09:46:45 mrg Exp $
# from: @(#)Makefile 8.1 (Berkeley) 6/4/93
PROG= tftpd
SRCS= tftpd.c tftpsubs.c
MAN= tftpd.8
CFLAGS+=-I${.CURDIR}/../../usr.bin/tftp
.PATH: ${.CURDIR}/../../usr.bin/tftp
.include <bsd.prog.mk>

View File

@ -1,5 +1,7 @@
.\" Copyright (c) 1983, 1991 The Regents of the University of California.
.\" All rights reserved.
.\" $NetBSD: tftpd.8,v 1.4 1997/10/07 09:46:46 mrg Exp $
.\"
.\" Copyright (c) 1983, 1991, 1993
.\" The 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
@ -29,23 +31,22 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" from: @(#)tftpd.8 6.7 (Berkeley) 5/13/91
.\" $Id: tftpd.8,v 1.3 1994/01/10 16:29:46 mycroft Exp $
.\"
.Dd May 13, 1991
.\" from: @(#)tftpd.8 8.1 (Berkeley) 6/4/93
.\"
.Dd June 4, 1993
.Dt TFTPD 8
.Os BSD 4.2
.Sh NAME
.Nm tftpd
.Nd
.Tn DARPA
Trivial File Transfer Protocol server
Internet Trivial File Transfer Protocol server
.Sh SYNOPSIS
.Nm tftpd
.Op Fl s Ar directory
.Op Fl l
.Op Fl n
.Op Ar directory ...
.Nm tftpd
.Fl s
.Op Ar directory
.Sh DESCRIPTION
.Nm Tftpd
is a server which supports the
@ -70,6 +71,7 @@ Due to the lack of authentication information,
.Nm tftpd
will allow only publicly readable files to be
accessed.
Files containing the string ``/\|\fB.\|.\fP\|/'' are not allowed.
Files may be written only if they already exist and are publicly writable.
Note that this extends the concept of
.Dq public
@ -81,19 +83,31 @@ The server should have the user ID with the lowest possible privilege.
.Pp
Access to files may be restricted by invoking
.Nm tftpd
with a list of directories by including pathnames
with a list of directories by including up to 20 pathnames
as server program arguments in
.Pa /etc/inetd.conf .
In this case access is restricted to files whose
names are prefixed by the one of the given directories.
The given directories are also treated as a search path for
relative filename requests.
.Pp
When using the
.Fl s
flag with a directory name, tftpd will
The options are:
.Bl -tag -width Ds
.It Fl l
Logs all requests using
.Xr syslog 3 .
.It Fl n
Suppresses negative acknowledgement of requests for nonexistent
relative filenames.
.It Fl s
.Nm
will
.Xr chroot 2
on startup; therefore the remote host is not expected to pass the directory
as part of the file name to transfer. This option is intended primarily for
compatibility with SunOS boot ROMs which do not include a directory name.
.El
.Pp
.Sh SEE ALSO
.Xr tftp 1 ,
.Xr inetd 8

View File

@ -1,6 +1,8 @@
/* $NetBSD: tftpd.c,v 1.9 1997/10/07 09:46:47 mrg Exp $ */
/*
* Copyright (c) 1983 Regents of the University of California.
* All rights reserved.
* Copyright (c) 1983, 1993
* The 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
@ -31,41 +33,49 @@
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1983 Regents of the University of California.\n\
All rights reserved.\n";
#endif /* not lint */
#ifndef lint
/*static char sccsid[] = "from: @(#)tftpd.c 5.13 (Berkeley) 2/26/91";*/
static char rcsid[] = "$Id: tftpd.c,v 1.8 1997/04/22 10:33:07 mrg Exp $";
__COPYRIGHT("@(#) Copyright (c) 1983, 1993\n\
The Regents of the University of California. All rights reserved.\n");
#if 0
static char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93";
#else
__RCSID("$NetBSD: tftpd.c,v 1.9 1997/10/07 09:46:47 mrg Exp $");
#endif
#endif /* not lint */
/*
* Trivial file transfer protocol server.
*
* This version includes many modifications by Jim Guyton <guyton@rand-unix>
* This version includes many modifications by Jim Guyton
* <guyton@rand-unix>.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/tftp.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <setjmp.h>
#include <syslog.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "tftpsubs.h"
/* XXX svr4 defines UID_NOBODY and GID_NOBODY constants in <sys/param.h> */
#define UID_NOBODY 32767
@ -73,9 +83,7 @@ static char rcsid[] = "$Id: tftpd.c,v 1.8 1997/04/22 10:33:07 mrg Exp $";
#define TIMEOUT 5
extern int errno;
extern char *__progname;
struct sockaddr_in s_in = { AF_INET };
int peer;
int rexmtval = TIMEOUT;
int maxtimeout = 5*TIMEOUT;
@ -86,10 +94,51 @@ char ackbuf[PKTSIZE];
struct sockaddr_in from;
int fromlen;
#define MAXARG 4
char *dirs[MAXARG+1];
/*
* Null-terminated directory prefix list for absolute pathname requests and
* search list for relative pathname requests.
*
* MAXDIRS should be at least as large as the number of arguments that
* inetd allows (currently 20).
*/
#define MAXDIRS 20
static struct dirlist {
char *name;
int len;
} dirs[MAXDIRS+1];
static int suppress_naks;
static int logging;
static int secure;
static char *securedir;
int secure = 0;
struct formats;
static void tftp __P((struct tftphdr *, int));
static char *errtomsg __P((int));
static void nak __P((int));
static char *verifyhost __P((struct sockaddr_in *));
static void usage __P((void));
void timer __P((int));
void sendfile __P((struct formats *));
void recvfile __P((struct formats *));
void justquit __P((int));
int validate_access __P((char **, int));
int main __P((int, char **));
struct formats {
char *f_mode;
int (*f_validate) __P((char **, int));
void (*f_send) __P((struct formats *));
void (*f_recv) __P((struct formats *));
int f_convert;
} formats[] = {
{ "netascii", validate_access, sendfile, recvfile, 1 },
{ "octet", validate_access, sendfile, recvfile, 0 },
#ifdef notdef
{ "mail", validate_user, sendmail, recvmail, 1 },
#endif
{ 0 }
};
static void
usage()
@ -98,22 +147,32 @@ usage()
exit(1);
}
int
main(argc, argv)
int argc;
char **argv;
{
register struct tftphdr *tp;
register int n = 0;
int on = 1;
int ch, on;
int fd = 0;
int c;
struct sockaddr_in sin;
openlog("tftpd", LOG_PID, LOG_DAEMON);
while ((c = getopt(argc, argv, "s")) != -1)
switch (c) {
while ((ch = getopt(argc, argv, "lns:")) != -1)
switch (ch) {
case 'l':
logging = 1;
break;
case 'n':
suppress_naks = 1;
break;
case 's':
secure = 1;
securedir = optarg;
break;
default:
@ -121,23 +180,29 @@ main(argc, argv)
break;
}
for (; optind != argc; optind++) {
if (!secure) {
if (n >= MAXARG) {
syslog(LOG_ERR, "too many directories\n");
exit(1);
} else
dirs[n++] = argv[optind];
}
if (chdir(argv[optind])) {
syslog(LOG_ERR, "%s: %m\n", argv[optind]);
exit(1);
if (optind < argc) {
struct dirlist *dirp;
/* Get list of directory prefixes. Skip relative pathnames. */
for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS];
optind++) {
if (argv[optind][0] == '/') {
dirp->name = argv[optind];
dirp->len = strlen(dirp->name);
dirp++;
}
}
}
if (secure && chroot(".")) {
syslog(LOG_ERR, "chroot: %m\n");
exit(1);
if (secure) {
if (chdir(securedir) < 0) {
syslog(LOG_ERR, "chdir %s: %m", securedir);
exit(1);
}
if (chroot(".")) {
syslog(LOG_ERR, "chroot: %m\n");
exit(1);
}
}
if (setgid(GID_NOBODY)) {
@ -155,6 +220,7 @@ main(argc, argv)
exit(1);
}
on = 1;
if (ioctl(fd, FIONBIO, &on) < 0) {
syslog(LOG_ERR, "ioctl(FIONBIO): %m\n");
exit(1);
@ -226,7 +292,9 @@ main(argc, argv)
syslog(LOG_ERR, "socket: %m\n");
exit(1);
}
if (bind(peer, (struct sockaddr *)&s_in, sizeof (s_in)) < 0) {
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
syslog(LOG_ERR, "bind: %m\n");
exit(1);
}
@ -241,27 +309,10 @@ main(argc, argv)
exit(1);
}
int validate_access();
int sendfile(), recvfile();
struct formats {
char *f_mode;
int (*f_validate)();
int (*f_send)();
int (*f_recv)();
int f_convert;
} formats[] = {
{ "netascii", validate_access, sendfile, recvfile, 1 },
{ "octet", validate_access, sendfile, recvfile, 0 },
#ifdef notdef
{ "mail", validate_user, sendmail, recvmail, 1 },
#endif
{ 0 }
};
/*
* Handle initial connection protocol.
*/
static void
tftp(tp, size)
struct tftphdr *tp;
int size;
@ -269,7 +320,7 @@ tftp(tp, size)
register char *cp;
int first = 1, ecode;
register struct formats *pf;
char *filename, *mode;
char *filename, *mode = NULL; /* XXX gcc */
filename = cp = tp->th_stuff;
again:
@ -297,8 +348,20 @@ again:
nak(EBADOP);
exit(1);
}
ecode = (*pf->f_validate)(filename, tp->th_opcode);
ecode = (*pf->f_validate)(&filename, tp->th_opcode);
if (logging) {
syslog(LOG_INFO, "%s: %s request for %s: %s",
verifyhost(&from),
tp->th_opcode == WRQ ? "write" : "read",
filename, errtomsg(ecode));
}
if (ecode) {
/*
* Avoid storms of naks to a RRQ broadcast for a relative
* bootfile pathname from a diskless Sun.
*/
if (suppress_naks && *filename != '/' && ecode == ENOTFOUND)
exit(0);
nak(ecode);
exit(1);
}
@ -323,38 +386,84 @@ FILE *file;
* Note also, full path name must be
* given as we have no login directory.
*/
validate_access(filename, mode)
char *filename;
int
validate_access(filep, mode)
char **filep;
int mode;
{
struct stat stbuf;
int fd;
char *cp, **dirp;
struct dirlist *dirp;
static char pathname[MAXPATHLEN];
char *filename = *filep;
if (!secure) {
if (*filename != '/')
return (EACCESS);
/*
* Prevent tricksters from getting around the directory restrictions
*/
if (strstr(filename, "/../"))
return (EACCESS);
if (*filename == '/') {
/*
* prevent tricksters from getting around the directory
* restrictions
* Allow the request if it's in one of the approved locations.
* Special case: check the null prefix ("/") by looking
* for length = 1 and relying on the arg. processing that
* it's a /.
*/
for (cp = filename + 1; *cp; cp++)
if(*cp == '.' && strncmp(cp-1, "/../", 4) == 0)
return(EACCESS);
for (dirp = dirs; *dirp; dirp++)
if (strncmp(filename, *dirp, strlen(*dirp)) == 0)
break;
if (*dirp==0 && dirp!=dirs)
return (EACCESS);
}
if (stat(filename, &stbuf) < 0)
return (errno == ENOENT ? ENOTFOUND : EACCESS);
if (mode == RRQ) {
if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
for (dirp = dirs; dirp->name != NULL; dirp++) {
if (dirp->len == 1 ||
(!strncmp(filename, dirp->name, dirp->len) &&
filename[dirp->len] == '/'))
break;
}
/* If directory list is empty, allow access to any file */
if (dirp->name == NULL && dirp != dirs)
return (EACCESS);
if (stat(filename, &stbuf) < 0)
return (errno == ENOENT ? ENOTFOUND : EACCESS);
if ((stbuf.st_mode & S_IFMT) != S_IFREG)
return (ENOTFOUND);
if (mode == RRQ) {
if ((stbuf.st_mode & S_IROTH) == 0)
return (EACCESS);
} else {
if ((stbuf.st_mode & S_IWOTH) == 0)
return (EACCESS);
}
} else {
if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
int err;
/*
* Relative file name: search the approved locations for it.
* Don't allow write requests or ones that avoid directory
* restrictions.
*/
if (mode != RRQ || !strncmp(filename, "../", 3))
return (EACCESS);
/*
* If the file exists in one of the directories and isn't
* readable, continue looking. However, change the error code
* to give an indication that the file exists.
*/
err = ENOTFOUND;
if (dirs[0].name != NULL) {
for (dirp = dirs; dirp->name != NULL; dirp++) {
snprintf(pathname, sizeof pathname, "%s/%s",
dirp->name, filename);
if (stat(pathname, &stbuf) == 0 &&
(stbuf.st_mode & S_IFMT) == S_IFREG) {
if ((stbuf.st_mode & S_IROTH) != 0)
break;
err = EACCESS;
}
}
if (dirp->name == NULL)
return (err);
*filep = filename = pathname;
} else
*filep = filename;
}
fd = open(filename, mode == RRQ ? 0 : 1);
if (fd < 0)
@ -370,7 +479,8 @@ int timeout;
jmp_buf timeoutbuf;
void
timer()
timer(dummy)
int dummy;
{
timeout += rexmtval;
@ -382,16 +492,19 @@ timer()
/*
* Send the requested file.
*/
void
sendfile(pf)
struct formats *pf;
{
struct tftphdr *dp, *r_init();
struct tftphdr *dp;
register struct tftphdr *ap; /* ack packet */
register int block = 1, size, n;
register int size, n;
volatile int block;
signal(SIGALRM, timer);
dp = r_init();
ap = (struct tftphdr *)ackbuf;
block = 1;
do {
size = readit(file, &dp, pf->f_convert);
if (size < 0) {
@ -401,7 +514,7 @@ sendfile(pf)
dp->th_opcode = htons((u_short)DATA);
dp->th_block = htons((u_short)block);
timeout = 0;
(void) setjmp(timeoutbuf);
(void)setjmp(timeoutbuf);
send_data:
if (send(peer, dp, size + 4, 0) != size + 4) {
@ -422,16 +535,14 @@ send_data:
if (ap->th_opcode == ERROR)
goto abort;
if (ap->th_opcode == ACK) {
if (ap->th_block == block) {
if (ap->th_block == block)
break;
}
/* Re-synchronize with the other side */
(void) synchnet(peer);
if (ap->th_block == (block -1)) {
if (ap->th_block == (block -1))
goto send_data;
}
}
}
@ -442,25 +553,28 @@ abort:
}
void
justquit()
justquit(dummy)
int dummy;
{
exit(0);
}
/*
* Receive a file.
*/
void
recvfile(pf)
struct formats *pf;
{
struct tftphdr *dp, *w_init();
struct tftphdr *dp;
register struct tftphdr *ap; /* ack buffer */
register int block = 0, n, size;
register int n, size;
volatile int block;
signal(SIGALRM, timer);
dp = w_init();
ap = (struct tftphdr *)ackbuf;
block = 0;
do {
timeout = 0;
ap->th_opcode = htons((u_short)ACK);
@ -538,12 +652,29 @@ struct errmsg {
{ -1, 0 }
};
static char *
errtomsg(error)
int error;
{
static char buf[20];
register struct errmsg *pe;
if (error == 0)
return "success";
for (pe = errmsgs; pe->e_code >= 0; pe++)
if (pe->e_code == error)
return pe->e_msg;
sprintf(buf, "error %d", error);
return buf;
}
/*
* 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(error)
int error;
{
@ -568,3 +699,17 @@ nak(error)
if (send(peer, buf, length, 0) != length)
syslog(LOG_ERR, "nak: %m\n");
}
static char *
verifyhost(fromp)
struct sockaddr_in *fromp;
{
struct hostent *hp;
hp = gethostbyaddr((char *)&fromp->sin_addr, sizeof (fromp->sin_addr),
fromp->sin_family);
if (hp)
return hp->h_name;
else
return inet_ntoa(fromp->sin_addr);
}