Convert STATS and PARANOID to run-time options.

Document the new -R (relax paranoia) option.

From NetBSD/Lite2: code and man page cleanups, Kerberos IV hooks
(relax, we're still exportable), and /etc/ftpchroot feature for
semi-anonymous accounts
This commit is contained in:
Paul Traina 1996-08-05 00:21:15 +00:00
parent db330c9349
commit a5a4544e77
5 changed files with 144 additions and 79 deletions

View File

@ -4,11 +4,18 @@ PROG= ftpd
MAN8= ftpd.8
SRCS= ftpd.c ftpcmd.c logwtmp.c popen.c skey-stuff.c
CFLAGS+=-DSETPROCTITLE -DSKEY -DSTATS -DPARANOID
CFLAGS+=-DSETPROCTITLE -DSKEY -Wall
LDADD= -lskey -lmd -lcrypt -lutil
DPADD= ${LIBSKEY} ${LIBMD} ${LIBCRYPT} ${LIBUTIL}
CLEANFILES+=ftpcmd.c y.tab.h
.if defined(KERBEROS)
SRCS+= klogin.c
LDADD+= -lkrb -ldes
DPADD+= ${LIBKRB} ${LIBDES}
CFLAGS+=-DKERBEROS
.endif
.include <bsd.prog.mk>

View File

@ -71,6 +71,7 @@ extern struct sockaddr_in data_dest, his_addr;
extern int logged_in;
extern struct passwd *pw;
extern int guest;
extern int paranoid;
extern int logging;
extern int type;
extern int form;
@ -152,19 +153,16 @@ cmd
| PORT check_login SP host_port CRLF
{
if ($2) {
#ifdef PARANOID
if ((ntohs(data_dest.sin_port) <
IPPORT_RESERVED) ||
memcmp(&data_dest.sin_addr,
&his_addr.sin_addr,
sizeof(data_dest.sin_addr)))
{
if (paranoid &&
((ntohs(data_dest.sin_port) <
IPPORT_RESERVED) ||
memcmp(&data_dest.sin_addr,
&his_addr.sin_addr,
sizeof(data_dest.sin_addr)))) {
usedefault = 1;
reply(500,
"Illegal PORT range rejected.");
} else
#endif
{
} else {
usedefault = 0;
if (pdata >= 0) {
(void) close(pdata);
@ -510,8 +508,9 @@ cmd
struct tm *t;
t = gmtime(&stbuf.st_mtime);
reply(213,
"19%02d%02d%02d%02d%02d%02d",
t->tm_year, t->tm_mon+1, t->tm_mday,
"%04d%02d%02d%02d%02d%02d",
1900 + t->tm_year,
t->tm_mon+1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
}
}
@ -572,11 +571,12 @@ host_port
{
char *a, *p;
a = (char *)&data_dest.sin_addr;
a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
data_dest.sin_len = sizeof(struct sockaddr_in);
data_dest.sin_family = AF_INET;
p = (char *)&data_dest.sin_port;
p[0] = $9; p[1] = $11;
data_dest.sin_family = AF_INET;
a = (char *)&data_dest.sin_addr;
a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
}
;

View File

@ -42,6 +42,7 @@ Internet File Transfer Protocol server
.Nm ftpd
.Op Fl dl
.Op Fl D
.Op Fl R
.Op Fl S
.Op Fl U
.Op Fl T Ar maxtimeout
@ -79,6 +80,17 @@ starting
from
.Xr inetd 8
and is thus useful on busy servers to reduce load.
.It Fl R
With this option set,
.Nm ftpd
will revert to historical behavior with regard to security checks on
user operations and restrictions on PORT requests.
Currently,
.Nm ftpd
will only honor PORT commands directed to unprivileged ports on the
remote user's host (which violates the FTP protocol specification but
closes some security holes).
.
.It Fl S
With this option set,
.Nm ftpd
@ -209,15 +221,23 @@ This allows users to utilize the metacharacters
.Dq Li \&*?[]{}~ .
.Pp
.Nm Ftpd
authenticates users according to three rules.
authenticates users according to five rules.
.Pp
.Bl -enum -offset indent
.It
The login name must be in the password data base,
.Pa /etc/passwd ,
.Pa /etc/pwd.db ,
and not have a null password.
In this case a password must be provided by the client before any
file operations may be performed.
If the user has an S/Key key, the response from a successful USER
command will include an S/Key challenge. The client may choose to respond
with a PASS command giving either a standard password or an S/Key
one-time password. The server will automatically determine which type of
password it has been given and attempt to authenticate accordingly. See
.Xr key 1
for more information on S/Key authentication. S/Key is a Trademark of
Bellcore.
.It
The login name must not appear in the file
.Pa /etc/ftpusers .
@ -225,6 +245,19 @@ The login name must not appear in the file
The user must have a standard shell returned by
.Xr getusershell 3 .
.It
If the user name appears in the file
.Pa /etc/ftpchroot
the session's root will be changed to the user's login directory by
.Xr chroot 2
as for an
.Dq anonymous
or
.Dq ftp
account (see next item). However, the user must still supply a password.
This feature is intended as a compromise between a fully anonymous account
and a fully privileged account. The account should also be set up as for an
anonymous account.
.It
If the user name is
.Dq anonymous
or
@ -235,7 +268,8 @@ file (user
.Dq ftp ) .
In this case the user is allowed
to log in by specifying any password (by convention an email address for
the user should be used as the password). When the
the user should be used as the password).
When the
.Fl S
option is set, all transfers are logged as well.
.El
@ -269,8 +303,8 @@ This program should be mode 111.
Make this directory owned by
.Dq root
and unwritable by anyone (mode 555).
The files
.Xr passwd 5
The files pwd.db (see
.Xr passwd 5 )
and
.Xr group 5
must be present for the
@ -294,6 +328,8 @@ account in this directory.
.Bl -tag -width /etc/ftpwelcome -compact
.It Pa /etc/ftpusers
List of unwelcome/restricted users.
.It Pa /etc/ftpchroot
List of normal users who should be chroot'd.
.It Pa /etc/ftpwelcome
Welcome notice.
.It Pa /etc/ftpmotd
@ -305,6 +341,7 @@ Log file for anonymous transfers.
.El
.Sh SEE ALSO
.Xr ftp 1 ,
.Xr key 1 ,
.Xr getusershell 3 ,
.Xr inetd 8 ,
.Xr syslogd 8

View File

@ -30,7 +30,7 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: ftpd.c,v 1.17 1996/05/31 03:10:25 peter Exp $
* $Id: ftpd.c,v 1.18 1996/08/04 22:40:35 pst Exp $
*/
#ifndef lint
@ -117,11 +117,11 @@ int timeout = 900; /* timeout after 15 minutes of inactivity */
int maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
int logging;
int restricted_data_ports = 1;
int paranoid = 1; /* be extra careful about security */
int guest;
#ifdef STATS
int dochroot;
int stats;
int statfd = -1;
#endif
int type;
int form;
int stru; /* avoid C keyword */
@ -139,8 +139,14 @@ int defumask = CMASK; /* default umask value */
char tmpline[7];
char hostname[MAXHOSTNAMELEN];
char remotehost[MAXHOSTNAMELEN];
#ifdef STATS
char *ident = NULL;
static char ttyline[20];
char *tty = ttyline; /* for klogin */
#if defined(KERBEROS)
int notickets = 1;
char *krbtkfile_env = NULL;
#endif
/*
@ -188,7 +194,7 @@ char addr_string[20]; /* XXX */
static void ack __P((char *));
static void myoob __P((int));
static int checkuser __P((char *));
static int checkuser __P((char *, char *));
static FILE *dataconn __P((char *, off_t, char *));
static void dolog __P((struct sockaddr_in *));
static char *curdir __P((void));
@ -202,9 +208,7 @@ static struct passwd *
sgetpwnam __P((char *));
static char *sgetsave __P((char *));
static void reapchild __P((int));
#ifdef STATS
static void logxfer __P((char *, long, long));
#endif
static char *
curdir()
@ -242,26 +246,32 @@ main(argc, argv, envp)
#endif /* OLD_SETPROCTITLE */
#ifdef STATS
while ((ch = getopt(argc, argv, "dlDSt:T:u:v")) != EOF) {
#else
while ((ch = getopt(argc, argv, "dlDUt:T:u:v")) != EOF) {
#endif
while ((ch = getopt(argc, argv, "dlDSUt:T:u:v")) != EOF) {
switch (ch) {
case 'D':
daemon_mode++;
break;
case 'd':
debug = 1;
debug++;
break;
case 'l':
logging++; /* > 1 == extra logging */
break;
case 'U':
restricted_data_ports = 0;
case 'R':
paranoid = 0;
break;
case 'S':
stats++;
break;
case 'T':
maxtimeout = atoi(optarg);
if (timeout > maxtimeout)
timeout = maxtimeout;
break;
case 't':
@ -269,15 +279,9 @@ main(argc, argv, envp)
if (maxtimeout < timeout)
maxtimeout = timeout;
break;
#ifdef STATS
case 'S':
stats = 1;
break;
#endif
case 'T':
maxtimeout = atoi(optarg);
if (timeout > maxtimeout)
timeout = maxtimeout;
case 'U':
restricted_data_ports = 0;
break;
case 'u':
@ -397,6 +401,9 @@ main(argc, argv, envp)
#endif
data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1);
/* set this here so klogin can use it... */
(void)sprintf(ttyline, "ftp%d", getpid());
/* Try to handle urgent data inline */
#ifdef SO_OOBINLINE
if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
@ -458,8 +465,6 @@ lostconn(signo)
dologout(-1);
}
static char ttyline[20];
/*
* Helper function for sgetpwnam().
*/
@ -533,13 +538,17 @@ user(name)
if (guest) {
reply(530, "Can't change user from guest login.");
return;
} else if (dochroot) {
reply(530, "Can't change user from chroot user.");
return;
}
end_login();
}
guest = 0;
if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
if (checkuser("ftp") || checkuser("anonymous"))
if (checkuser(_PATH_FTPUSERS, "ftp") ||
checkuser(_PATH_FTPUSERS, "anonymous"))
reply(530, "User %s access denied.", name);
else if ((pw = sgetpwnam("ftp")) != NULL) {
guest = 1;
@ -561,7 +570,7 @@ user(name)
break;
endusershell();
if (cp == NULL || checkuser(name)) {
if (cp == NULL || checkuser(_PATH_FTPUSERS, name)) {
reply(530, "User %s access denied.", name);
if (logging)
syslog(LOG_NOTICE,
@ -589,17 +598,18 @@ user(name)
}
/*
* Check if a user is in the file _PATH_FTPUSERS
* Check if a user is in the file "fname"
*/
static int
checkuser(name)
checkuser(fname, name)
char *fname;
char *name;
{
FILE *fd;
int found = 0;
char *p, line[BUFSIZ];
if ((fd = fopen(_PATH_FTPUSERS, "r")) != NULL) {
if ((fd = fopen(fname, "r")) != NULL) {
while (fgets(line, sizeof(line), fd) != NULL)
if ((p = strchr(line, '\n')) != NULL) {
*p = '\0';
@ -629,13 +639,14 @@ end_login()
pw = NULL;
logged_in = 0;
guest = 0;
dochroot = 0;
}
void
pass(passwd)
char *passwd;
{
char *salt, *xpasswd;
int rval;
FILE *fd;
static char homedir[MAXPATHLEN];
@ -645,20 +656,33 @@ pass(passwd)
}
askpasswd = 0;
if (!guest) { /* "ftp" is only account allowed no password */
if (pw == NULL)
salt = "xx";
else
salt = pw->pw_passwd;
if (pw == NULL) {
rval = 1; /* failure below */
goto skip;
}
#if defined(KERBEROS)
rval = klogin(pw, "", hostname, passwd);
if (rval == 0)
goto skip;
#endif
#ifdef SKEY
xpasswd = skey_crypt(passwd, salt, pw, pwok);
rval = strcmp(skey_crypt(passwd, pw->pw_passwd, pw, pwok),
passwd);
pwok = 0;
#else
xpasswd = crypt(passwd, salt);
rval = strcmp(crypt(passwd, pw->passwd), passwd);
#endif
/* The strcmp does not catch null passwords! */
if (pw == NULL || *pw->pw_passwd == '\0' ||
(pw->pw_expire && time(NULL) >= pw->pw_expire) ||
strcmp(xpasswd, pw->pw_passwd)) {
if (*pw->pw_passwd == '\0' ||
(pw->pw_expire && time(NULL) >= pw->pw_expire))
rval = 1; /* failure */
skip:
/*
* If rval == 1, the user failed the authentication check
* above. If rval == 0, either Kerberos or local authentication
* succeeded.
*/
if (rval) {
reply(530, "Login incorrect.");
if (logging)
syslog(LOG_NOTICE,
@ -682,16 +706,14 @@ pass(passwd)
(void) initgroups(pw->pw_name, pw->pw_gid);
/* open wtmp before chroot */
(void)sprintf(ttyline, "ftp%d", getpid());
logwtmp(ttyline, pw->pw_name, remotehost);
logged_in = 1;
#ifdef STATS
if (guest && stats == 1 && statfd < 0)
if (guest && stats && statfd < 0)
if ((statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND)) < 0)
stats = 0;
#endif
dochroot = checkuser(_PATH_FTPCHROOT, pw->pw_name);
if (guest) {
/*
* We MUST do a chdir() after the chroot. Otherwise
@ -702,6 +724,11 @@ pass(passwd)
reply(550, "Can't set guest privileges.");
goto bad;
}
} else if (dochroot) {
if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
reply(550, "Can't change root.");
goto bad;
}
} else if (chdir(pw->pw_dir) < 0) {
if (chdir("/") < 0) {
reply(530, "User %s: can't change directory to %s.",
@ -737,16 +764,12 @@ pass(passwd)
(void) fclose(fd);
}
if (guest) {
#ifdef STATS
char * copy();
if (ident != NULL)
free(ident);
ident = strdup(passwd);
if (ident == NULL)
fatal("Ran out of memory.");
#endif
reply(230, "Guest login ok, access restrictions apply.");
#ifdef SETPROCTITLE
snprintf(proctitle, sizeof(proctitle),
@ -783,9 +806,7 @@ retrieve(cmd, name)
FILE *fin, *dout;
struct stat st;
int (*closefunc) __P((FILE *));
#ifdef STATS
long start;
#endif
if (cmd == 0) {
fin = fopen(name, "r"), closefunc = fclose;
@ -835,15 +856,11 @@ retrieve(cmd, name)
dout = dataconn(name, st.st_size, "w");
if (dout == NULL)
goto done;
#ifdef STATS
time(&start);
#endif
send_data(fin, dout, st.st_blksize, st.st_size,
restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode));
#ifdef STATS
if (cmd == 0 && guest && stats)
logxfer(name, st.st_size, start);
#endif
(void) fclose(dout);
data = -1;
pdata = -1;
@ -941,6 +958,7 @@ getdatasock(mode)
(char *) &on, sizeof(on)) < 0)
goto bad;
/* anchor socket to avoid multi-homing problems */
data_source.sin_len = sizeof(struct sockaddr_in);
data_source.sin_family = AF_INET;
data_source.sin_addr = ctrl_addr.sin_addr;
for (tries = 1; ; tries++) {
@ -1022,7 +1040,7 @@ dataconn(name, size, mode)
(void) close(pdata);
pdata = s;
#ifdef IP_TOS
tos = IPTOS_LOWDELAY;
tos = IPTOS_THROUGHPUT;
(void) setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos,
sizeof(int));
#endif
@ -1578,6 +1596,10 @@ dologout(status)
if (logged_in) {
(void) seteuid((uid_t)0);
logwtmp(ttyline, "", "");
#if defined(KERBEROS)
if (!notickets && krbtkfile_env)
unlink(krbtkfile_env);
#endif
}
/* beware of flushing buffers after a SIGPIPE */
_exit(status);
@ -1906,7 +1928,6 @@ setproctitle(fmt, va_alist)
}
#endif /* OLD_SETPROCTITLE */
#ifdef STATS
static void
logxfer(name, size, start)
char *name;
@ -1925,4 +1946,3 @@ logxfer(name, size, start)
write(statfd, buf, strlen(buf));
}
}
#endif

View File

@ -35,6 +35,7 @@
#include <paths.h>
#define _PATH_FTPCHROOT "/etc/ftpchroot"
#define _PATH_FTPWELCOME "/etc/ftpwelcome"
#define _PATH_FTPLOGINMESG "/etc/ftpmotd"
#define _PATH_FTPDSTATFILE "/var/log/ftpd"