diff --git a/CHANGES b/CHANGES index f2d3880..77efcc8 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,10 @@ Changes in 0.27: Allow replacement patterns to include the IP address of the requesting host (\i). + Allow relying on Unix permissions rather than o+r magic if the + -p option is specified. As part of this, set all groups if + initgroups() is specified on the platform. + Changes in 0.26: Fix the configuration process so tftpd doesn't end up diff --git a/config.h b/config.h index 187009b..edd6781 100644 --- a/config.h +++ b/config.h @@ -85,6 +85,10 @@ #endif #endif +#ifdef HAVE_GRP_H +#include +#endif + #include #include diff --git a/configure.in b/configure.in index a502d02..cef754a 100644 --- a/configure.in +++ b/configure.in @@ -56,6 +56,7 @@ AC_HEADER_STDC AC_CHECK_HEADERS(inttypes.h) AC_CHECK_HEADERS(stdint.h) PA_CHECK_INTTYPES_H_SANE +AC_CHECK_HEADERS(grp.h) AC_CHECK_HEADERS(libgen.h) AC_CHECK_HEADERS(memory.h) AC_CHECK_HEADERS(setjmp.h) @@ -83,6 +84,8 @@ AC_CHECK_TYPES(u_short) AC_CHECK_TYPES(u_long) AC_TYPE_OFF_T AC_TYPE_PID_T +AC_TYPE_MODE_T +AC_TYPE_SIZE_T AC_SEARCH_LIBS(socket, socket, , [AC_MSG_ERROR(socket library not found)]) AC_SEARCH_LIBS(gethostbyname, [nsl resolv], , [AC_MSG_ERROR(gethostbyname not found)]) @@ -91,8 +94,12 @@ AC_SEARCH_LIBS(herror, [nsl resolv], , [AC_MSG_ERROR(herror not found)]) AC_CHECK_FUNCS(setsid) AC_CHECK_FUNCS(recvmsg) +AC_CHECK_FUNCS(ftruncate) AC_CHECK_FUNCS(setreuid) AC_CHECK_FUNCS(setregid) +AC_CHECK_FUNCS(initgroups) +AC_CHECK_FUNCS(setgroups) + dnl Solaris 8 has [u]intmax_t but not strtoumax(). How utterly braindamaged. AC_CHECK_FUNCS(strtoumax) AC_CHECK_FUNCS(strtoull) diff --git a/tftpd/tftpd.8 b/tftpd/tftpd.8 index d221bfd..b3ac0a7 100644 --- a/tftpd/tftpd.8 +++ b/tftpd/tftpd.8 @@ -31,7 +31,7 @@ .\" SUCH DAMAGE. .\" .\"----------------------------------------------------------------------- */ -.TH TFTPD 8 "16 November 2001" "tftp-hpa 0.27-pre1" "UNIX System Manager's Manual" +.TH TFTPD 8 "16 November 2001" "tftp-hpa 0.27-pre2" "UNIX System Manager's Manual" .SH NAME .B tftpd \- IPv4 Trivial File Transfer Protocol server @@ -225,7 +225,7 @@ The IP address of the requesting host, in dotted-quad notation The IP address of the requesting host, in hexadecimal notation (e.g. C00002A9). .TP -\fB\\\fP +\fB\\\\\fP Literal backslash. .TP \fB\\\fP\fIwhitespace\fP diff --git a/tftpd/tftpd.8.in b/tftpd/tftpd.8.in index 895e6d9..9ffbaf6 100644 --- a/tftpd/tftpd.8.in +++ b/tftpd/tftpd.8.in @@ -92,7 +92,23 @@ which cannot be easily made to include a directory name in its request. \fB\-u\fP \fIusername\fP Specify the username which .B tftpd -will run as; the default is "nobody". +will run as; the default is "nobody". The user ID, group ID, and (if +possible on the platform) the supplementary group IDs will be set to +the ones specified in the system permission database for this +username. +.TP +\fB\-U\fP \fIumask\fP +Sets the \fIumask\fP to the specified value. The default is zero +if the +.B \-p +option is not specified, or unchanged if +.B \-p is specified. +.TP +.B \-p +Indicate that no permissions checks beyond the normal system-provided +permission check for the user specified via the +.B \-u +option. .TP \fB\-t\fP \fItimeout\fP When run from @@ -243,9 +259,10 @@ process. The use of TFTP services does not require an account or password on the server system. Due to the lack of authentication information, .B tftpd -will allow only publicly readable files (o+r) to be accessed. Files -may be written only if they already exist and are publicly writable, -unless the +will allow only publicly readable files (o+r) to be accessed, unless the +.B \-p +option is specified. Files may be written only if they already exist +and are publicly writable, unless the .B \-c option is specified. Note that this extends the concept of ``public'' to include all users on all hosts that can be reached through the @@ -261,7 +278,7 @@ database for access control information. This may be slow; sites requiring maximum performance may want to compile without this option and rely on firewalling or kernel-based packet filters instead. .PP -The server should be set to have the user ID with the lowest possible +The server should be set to run as the user with the lowest possible privilege; please see the .B \-u flag. @@ -304,6 +321,7 @@ added patches by Markus Gutschke and Gero Kulhman. .SH "SEE ALSO" .BR tftp (1), .BR egrep (1), +.BR umask (2), .BR hosts_access (5), .BR regex (7), .BR inetd (8). diff --git a/tftpd/tftpd.c b/tftpd/tftpd.c index 21a7685..7fce678 100644 --- a/tftpd/tftpd.c +++ b/tftpd/tftpd.c @@ -103,6 +103,7 @@ const char **dirs; int secure = 0; int cancreate = 0; +int unixperms = 0; int verbosity = 0; @@ -184,11 +185,13 @@ main(int argc, char **argv) int standalone = 0; /* Standalone (listen) mode */ char *address = NULL; /* Address to listen to */ pid_t pid; + mode_t my_umask = 0; + int spec_umask = 0; int c; int setrv; int waittime = 900; /* Default time to wait for a connect*/ const char *user = "nobody"; /* Default user */ - char *p; + char *p, *ep; #ifdef WITH_REGEX char *rewrite_file = NULL; #endif @@ -200,7 +203,7 @@ main(int argc, char **argv) openlog(__progname, LOG_PID|LOG_NDELAY, LOG_DAEMON); - while ((c = getopt(argc, argv, "csvVla:u:r:t:m:")) != -1) + while ((c = getopt(argc, argv, "cspvVla:u:U:r:t:m:")) != -1) switch (c) { case 'c': cancreate = 1; @@ -208,6 +211,9 @@ main(int argc, char **argv) case 's': secure = 1; break; + case 'p': + unixperms = 1; + break; case 'l': standalone = 1; break; @@ -220,6 +226,14 @@ main(int argc, char **argv) case 'u': user = optarg; break; + case 'U': + my_umask = strtoul(optarg, &ep, 8); + if ( *ep ) { + syslog(LOG_ERR, "Invalid umask: %s", optarg); + exit(EX_USAGE); + } + spec_umask = 1; + break; case 'r': for ( opt = options ; opt->o_opt ; opt++ ) { if ( !strcasecmp(optarg, opt->o_opt) ) { @@ -280,6 +294,9 @@ main(int argc, char **argv) syslog(LOG_ERR, "no user %s: %m", user); exit(EX_NOUSER); } + + if ( spec_umask || !unixperms ) + umask(my_umask); if (ioctl(fd, FIONBIO, &on) < 0) { syslog(LOG_ERR, "ioctl(FIONBIO): %m"); @@ -474,13 +491,29 @@ main(int argc, char **argv) exit(EX_IOERR); } + /* Set up the supplementary group access list if possible */ + /* /etc/group still need to be accessible at this point */ +#ifdef HAVE_INITGROUPS + setrv = initgroups(user, pw->pw_gid); + if ( setrv ) { + syslog(LOG_ERR, "cannot set groups for user %s", user); + exit(EX_OSERR); + } +#else +#ifdef HAVE_SETGROUPS + if ( setgroups(0, NULL) ) { + syslog(LOG_ERR, "cannot clear group list"); + } +#endif +#endif + /* Chroot and drop privileges */ if (secure && chroot(".")) { syslog(LOG_ERR, "chroot: %m"); exit(EX_OSERR); } - + #ifdef HAVE_SETREGID setrv = setregid(pw->pw_gid, pw->pw_gid); #else @@ -885,47 +918,53 @@ validate_access(char *filename, int mode, struct formats *pf) * We use different a different permissions scheme if `cancreate' is * set. */ - wmode = O_TRUNC; - if (stat(filename, &stbuf) < 0) { - if (!cancreate) - return (errno == ENOENT ? ENOTFOUND : EACCESS); - else { - if ((errno == ENOENT) && (mode != RRQ)) - wmode |= O_CREAT; - else - return(EACCESS); + wmode = O_WRONLY | + (cancreate ? O_CREAT : 0) | + (unixperms ? O_TRUNC : 0); + + fd = open(filename, mode == RRQ ? O_RDONLY : wmode, 0666); + if (fd < 0) { + switch (errno) { + case ENOENT: + case ENOTDIR: + return ENOTFOUND; + case ENOSPC: + return ENOSPACE; + case EEXIST: + return EEXISTS; + default: + return EACCESS; } + } + + if ( fstat(fd, &stbuf) < 0 ) + exit(EX_OSERR); /* This shouldn't happen */ + + if (mode == RRQ) { + if ( !unixperms && (stbuf.st_mode & (S_IREAD >> 6)) == 0 ) + return (EACCESS); + tsize = stbuf.st_size; + /* We don't know the tsize if conversion is needed */ + tsize_ok = !pf->f_convert; } else { - if (mode == RRQ) { - if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) + if ( !unixperms ) { + if ( (stbuf.st_mode & (S_IWRITE >> 6)) == 0 ) return (EACCESS); - tsize = stbuf.st_size; - /* We don't know the tsize if conversion is needed */ - tsize_ok = !pf->f_convert; - } else { - if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) - return (EACCESS); - tsize = 0; - tsize_ok = 1; + + /* We didn't get to truncate the file at open() time */ +#ifdef HAVE_FTRUNCATE + if ( ftruncate(fd, (off_t)0) ) + return(EACCESS); +#endif } + tsize = 0; + tsize_ok = 1; } - fd = open(filename, mode == RRQ ? O_RDONLY : (O_WRONLY|wmode), 0666); - if (fd < 0) - return (errno + 100); - /* - * If the file was created, set default permissions. - */ - if ((wmode & O_CREAT) && fchmod(fd, 0666) < 0) { - int serrno = errno; - - close(fd); - unlink(filename); - - return (serrno + 100); - } + file = fdopen(fd, (mode == RRQ)? "r":"w"); if (file == NULL) - return (errno + 100); + exit(EX_OSERR); /* Internal error */ + return (0); }