diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 1995-10-18 08:53:40 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 1995-10-18 08:53:40 +0000 |
commit | d6583bb2a13f329cf0332ef2570eb8bb8fc0e39c (patch) | |
tree | ece253b876159b39c620e62b6c9b1174642e070e /libexec/tftpd |
initial import of NetBSD tree
Diffstat (limited to 'libexec/tftpd')
-rw-r--r-- | libexec/tftpd/Makefile | 9 | ||||
-rw-r--r-- | libexec/tftpd/tftpd.8 | 108 | ||||
-rw-r--r-- | libexec/tftpd/tftpd.c | 565 |
3 files changed, 682 insertions, 0 deletions
diff --git a/libexec/tftpd/Makefile b/libexec/tftpd/Makefile new file mode 100644 index 00000000000..7fc8cfae3c7 --- /dev/null +++ b/libexec/tftpd/Makefile @@ -0,0 +1,9 @@ +# from: @(#)Makefile 5.10 (Berkeley) 5/11/90 +# $Id: Makefile,v 1.1 1995/10/18 08:43:25 deraadt Exp $ + +PROG= tftpd +SRCS= tftpd.c tftpsubs.c +MAN= tftpd.8 +.PATH: ${.CURDIR}/../../usr.bin/tftp + +.include <bsd.prog.mk> diff --git a/libexec/tftpd/tftpd.8 b/libexec/tftpd/tftpd.8 new file mode 100644 index 00000000000..7a84973ac5d --- /dev/null +++ b/libexec/tftpd/tftpd.8 @@ -0,0 +1,108 @@ +.\" Copyright (c) 1983, 1991 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 +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)tftpd.8 6.7 (Berkeley) 5/13/91 +.\" $Id: tftpd.8,v 1.1 1995/10/18 08:43:25 deraadt Exp $ +.\" +.Dd May 13, 1991 +.Dt TFTPD 8 +.Os BSD 4.2 +.Sh NAME +.Nm tftpd +.Nd +.Tn DARPA +Trivial File Transfer Protocol server +.Sh SYNOPSIS +.Nm tftpd +.Op Ar directory ... +.Nm tftpd +.Fl s +.Op Ar directory +.Sh DESCRIPTION +.Nm Tftpd +is a server which supports the +.Tn DARPA +Trivial File Transfer +Protocol. +The +.Tn TFTP +server operates +at the port indicated in the +.Ql tftp +service description; +see +.Xr services 5 . +The server is normally started by +.Xr inetd 8 . +.Pp +The use of +.Xr tftp 1 +does not require an account or password on the remote system. +Due to the lack of authentication information, +.Nm tftpd +will allow only publicly readable files to be +accessed. +Files may be written only if they already exist and are publicly writable. +Note that this extends the concept of +.Dq public +to include +all users on all hosts that can be reached through the network; +this may not be appropriate on all systems, and its implications +should be considered before enabling tftp service. +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 +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. +.Pp +When using the +.Fl s +flag with a directory name, tftpd 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. +.Sh SEE ALSO +.Xr tftp 1 , +.Xr inetd 8 +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.2 . +.Pp +The +.Fl s +flag appeared in NetBSD 0.9a. diff --git a/libexec/tftpd/tftpd.c b/libexec/tftpd/tftpd.c new file mode 100644 index 00000000000..9b848b2816e --- /dev/null +++ b/libexec/tftpd/tftpd.c @@ -0,0 +1,565 @@ +/* + * Copyright (c) 1983 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#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.1 1995/10/18 08:43:25 deraadt Exp $"; +#endif /* not lint */ + +/* + * Trivial file transfer protocol server. + * + * This version includes many modifications by Jim Guyton <guyton@rand-unix> + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <signal.h> +#include <fcntl.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/tftp.h> +#include <netdb.h> + +#include <setjmp.h> +#include <syslog.h> +#include <stdio.h> +#include <errno.h> +#include <ctype.h> +#include <string.h> +#include <stdlib.h> + +/* XXX svr4 defines UID_NOBODY and GID_NOBODY constants in <sys/param.h> */ +#define UID_NOBODY 32767 +#define GID_NOBODY 32766 + +#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; + +#define PKTSIZE SEGSIZE+4 +char buf[PKTSIZE]; +char ackbuf[PKTSIZE]; +struct sockaddr_in from; +int fromlen; + +#define MAXARG 4 +char *dirs[MAXARG+1]; + +int secure = 0; + +static void +usage() +{ + syslog(LOG_ERR, "Usage: %s [-s] [directory ...]\n", __progname); + exit(1); +} + +main(argc, argv) + int argc; + char **argv; +{ + register struct tftphdr *tp; + register int n = 0; + int on = 1; + int fd = 0; + int c; + + openlog("tftpd", LOG_PID, LOG_DAEMON); + + while ((c = getopt(argc, argv, "s")) != -1) + switch (c) { + case 's': + secure = 1; + break; + + default: + usage(); + 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 (secure && chroot(".")) { + syslog(LOG_ERR, "chroot: %m\n"); + exit(1); + } + + if (setgid(GID_NOBODY)) { + syslog(LOG_ERR, "setgid: %m"); + exit(1); + } + + if (setuid(UID_NOBODY)) { + syslog(LOG_ERR, "setuid: %m"); + exit(1); + } + + if (ioctl(fd, FIONBIO, &on) < 0) { + syslog(LOG_ERR, "ioctl(FIONBIO): %m\n"); + exit(1); + } + fromlen = sizeof (from); + n = recvfrom(fd, buf, sizeof (buf), 0, + (struct sockaddr *)&from, &fromlen); + if (n < 0) { + syslog(LOG_ERR, "recvfrom: %m\n"); + exit(1); + } + /* + * Now that we have read the message out of the UDP + * socket, we fork and exit. Thus, inetd will go back + * to listening to the tftp port, and the next request + * to come in will start up a new instance of tftpd. + * + * We do this so that inetd can run tftpd in "wait" mode. + * The problem with tftpd running in "nowait" mode is that + * inetd may get one or more successful "selects" on the + * tftp port before we do our receive, so more than one + * instance of tftpd may be started up. Worse, if tftpd + * break before doing the above "recvfrom", inetd would + * spawn endless instances, clogging the system. + */ + { + int pid; + int i, j; + + for (i = 1; i < 20; i++) { + pid = fork(); + if (pid < 0) { + sleep(i); + /* + * flush out to most recently sent request. + * + * This may drop some request, but those + * will be resent by the clients when + * they timeout. The positive effect of + * this flush is to (try to) prevent more + * than one tftpd being started up to service + * a single request from a single client. + */ + j = sizeof from; + i = recvfrom(fd, buf, sizeof (buf), 0, + (struct sockaddr *)&from, &j); + if (i > 0) { + n = i; + fromlen = j; + } + } else { + break; + } + } + if (pid < 0) { + syslog(LOG_ERR, "fork: %m\n"); + exit(1); + } else if (pid != 0) { + exit(0); + } + } + from.sin_len = sizeof(struct sockaddr_in); + from.sin_family = AF_INET; + alarm(0); + close(fd); + close(1); + peer = socket(AF_INET, SOCK_DGRAM, 0); + if (peer < 0) { + syslog(LOG_ERR, "socket: %m\n"); + exit(1); + } + if (bind(peer, (struct sockaddr *)&s_in, sizeof (s_in)) < 0) { + syslog(LOG_ERR, "bind: %m\n"); + exit(1); + } + if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) { + syslog(LOG_ERR, "connect: %m\n"); + exit(1); + } + tp = (struct tftphdr *)buf; + tp->th_opcode = ntohs(tp->th_opcode); + if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) + tftp(tp, n); + 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. + */ +tftp(tp, size) + struct tftphdr *tp; + int size; +{ + register char *cp; + int first = 1, ecode; + register struct formats *pf; + char *filename, *mode; + + filename = cp = tp->th_stuff; +again: + while (cp < buf + size) { + if (*cp == '\0') + break; + cp++; + } + if (*cp != '\0') { + nak(EBADOP); + exit(1); + } + if (first) { + mode = ++cp; + first = 0; + goto again; + } + for (cp = mode; *cp; cp++) + if (isupper(*cp)) + *cp = tolower(*cp); + for (pf = formats; pf->f_mode; pf++) + if (strcmp(pf->f_mode, mode) == 0) + break; + if (pf->f_mode == 0) { + nak(EBADOP); + exit(1); + } + ecode = (*pf->f_validate)(filename, tp->th_opcode); + if (ecode) { + nak(ecode); + exit(1); + } + if (tp->th_opcode == WRQ) + (*pf->f_recv)(pf); + else + (*pf->f_send)(pf); + exit(0); +} + + +FILE *file; + +/* + * Validate file access. Since we + * have no uid or gid, for now require + * file to exist and be publicly + * readable/writable. + * If we were invoked with arguments + * from inetd then the file must also be + * in one of the given directory prefixes. + * Note also, full path name must be + * given as we have no login directory. + */ +validate_access(filename, mode) + char *filename; + int mode; +{ + struct stat stbuf; + int fd; + char *cp, **dirp; + + if (!secure) { + if (*filename != '/') + return (EACCESS); + /* + * prevent tricksters from getting around the directory + * restrictions + */ + 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) + return (EACCESS); + } else { + if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) + return (EACCESS); + } + fd = open(filename, mode == RRQ ? 0 : 1); + if (fd < 0) + return (errno + 100); + file = fdopen(fd, (mode == RRQ)? "r":"w"); + if (file == NULL) { + return errno+100; + } + return (0); +} + +int timeout; +jmp_buf timeoutbuf; + +void +timer() +{ + + timeout += rexmtval; + if (timeout >= maxtimeout) + exit(1); + longjmp(timeoutbuf, 1); +} + +/* + * Send the requested file. + */ +sendfile(pf) + struct formats *pf; +{ + struct tftphdr *dp, *r_init(); + register struct tftphdr *ap; /* ack packet */ + register int block = 1, size, n; + + signal(SIGALRM, timer); + dp = r_init(); + ap = (struct tftphdr *)ackbuf; + do { + size = readit(file, &dp, pf->f_convert); + if (size < 0) { + nak(errno + 100); + goto abort; + } + dp->th_opcode = htons((u_short)DATA); + dp->th_block = htons((u_short)block); + timeout = 0; + (void) setjmp(timeoutbuf); + +send_data: + if (send(peer, dp, size + 4, 0) != size + 4) { + syslog(LOG_ERR, "tftpd: write: %m\n"); + goto abort; + } + read_ahead(file, pf->f_convert); + for ( ; ; ) { + alarm(rexmtval); /* read the ack */ + n = recv(peer, ackbuf, sizeof (ackbuf), 0); + alarm(0); + if (n < 0) { + syslog(LOG_ERR, "tftpd: read: %m\n"); + goto abort; + } + ap->th_opcode = ntohs((u_short)ap->th_opcode); + ap->th_block = ntohs((u_short)ap->th_block); + + if (ap->th_opcode == ERROR) + goto abort; + + if (ap->th_opcode == ACK) { + if (ap->th_block == block) { + break; + } + /* Re-synchronize with the other side */ + (void) synchnet(peer); + if (ap->th_block == (block -1)) { + goto send_data; + } + } + + } + block++; + } while (size == SEGSIZE); +abort: + (void) fclose(file); +} + +void +justquit() +{ + exit(0); +} + + +/* + * Receive a file. + */ +recvfile(pf) + struct formats *pf; +{ + struct tftphdr *dp, *w_init(); + register struct tftphdr *ap; /* ack buffer */ + register int block = 0, n, size; + + signal(SIGALRM, timer); + dp = w_init(); + ap = (struct tftphdr *)ackbuf; + do { + timeout = 0; + ap->th_opcode = htons((u_short)ACK); + ap->th_block = htons((u_short)block); + block++; + (void) setjmp(timeoutbuf); +send_ack: + if (send(peer, ackbuf, 4, 0) != 4) { + syslog(LOG_ERR, "tftpd: write: %m\n"); + goto abort; + } + write_behind(file, pf->f_convert); + for ( ; ; ) { + alarm(rexmtval); + n = recv(peer, dp, PKTSIZE, 0); + alarm(0); + if (n < 0) { /* really? */ + syslog(LOG_ERR, "tftpd: read: %m\n"); + goto abort; + } + dp->th_opcode = ntohs((u_short)dp->th_opcode); + dp->th_block = ntohs((u_short)dp->th_block); + if (dp->th_opcode == ERROR) + goto abort; + if (dp->th_opcode == DATA) { + if (dp->th_block == block) { + break; /* normal */ + } + /* Re-synchronize with the other side */ + (void) synchnet(peer); + if (dp->th_block == (block-1)) + goto send_ack; /* rexmit */ + } + } + /* size = write(file, dp->th_data, n - 4); */ + size = writeit(file, &dp, n - 4, pf->f_convert); + if (size != (n-4)) { /* ahem */ + if (size < 0) nak(errno + 100); + else nak(ENOSPACE); + goto abort; + } + } while (size == SEGSIZE); + write_behind(file, pf->f_convert); + (void) fclose(file); /* close data file */ + + ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ + ap->th_block = htons((u_short)(block)); + (void) send(peer, ackbuf, 4, 0); + + signal(SIGALRM, justquit); /* just quit on timeout */ + alarm(rexmtval); + n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ + alarm(0); + 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 */ + (void) send(peer, ackbuf, 4, 0); /* resend final ack */ + } +abort: + return; +} + +struct errmsg { + int e_code; + char *e_msg; +} errmsgs[] = { + { EUNDEF, "Undefined error code" }, + { ENOTFOUND, "File not found" }, + { EACCESS, "Access violation" }, + { ENOSPACE, "Disk full or allocation exceeded" }, + { EBADOP, "Illegal TFTP operation" }, + { EBADID, "Unknown transfer ID" }, + { EEXISTS, "File already exists" }, + { ENOUSER, "No such user" }, + { -1, 0 } +}; + +/* + * Send a nak packet (error message). + * Error code passed in is one of the + * standard TFTP codes, or a UNIX errno + * offset by 100. + */ +nak(error) + int error; +{ + register struct tftphdr *tp; + int length; + register struct errmsg *pe; + + tp = (struct tftphdr *)buf; + tp->th_opcode = htons((u_short)ERROR); + tp->th_code = htons((u_short)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 */ + } + strcpy(tp->th_msg, pe->e_msg); + length = strlen(pe->e_msg); + tp->th_msg[length] = '\0'; + length += 5; + if (send(peer, buf, length, 0) != length) + syslog(LOG_ERR, "nak: %m\n"); +} |