--- ../lukemftpd-1.1/src/ftpd.8 2000-12-18 17:14:06.000000000 +1300 +++ src/ftpd.8 2003-12-03 21:41:47.000000000 +1300 @@ -172,6 +172,12 @@ If this option is specified more than once, the retrieve (get), store (put), append, delete, make directory, remove directory and rename operations and their file name arguments are also logged. +.It Fl p Ar passwdfile +Validate logins using +.Ar passwdfile +instead of the standard system login method. +.Ar passwdfile +must be in /etc/passwd format, with passwords included. .It Fl P Ar dataport Use .Ar dataport --- ../lukemftpd-1.1/src/ftpd.c 2003-12-03 22:56:50.000000000 +1300 +++ src/ftpd.c 2003-12-03 22:50:18.000000000 +1300 @@ -145,6 +145,8 @@ static const char *anondir = NULL; static const char *confdir = _DEFAULT_CONFDIR; +static const char *passwdfn = NULL; /* path to password file, if using alternate password file */ +static FILE *passwdfile = NULL; #if defined(KERBEROS) || defined(KERBEROS5) int has_ccache = 0; @@ -235,7 +237,7 @@ */ openlog("ftpd", LOG_PID | LOG_NDELAY, FTPD_LOGTYPE); - while ((ch = getopt(argc, argv, "a:c:C:de:h:HlP:qQrst:T:uUvV:wWX")) + while ((ch = getopt(argc, argv, "a:c:C:de:h:Hlp:P:qQrst:T:uUvV:wWX")) != -1) { switch (ch) { case 'a': @@ -274,6 +276,10 @@ logging++; /* > 1 == extra logging */ break; + case 'p': + passwdfn = optarg; + break; + case 'P': dataport = (int)strtol(optarg, &p, 10); if (*p != '\0' || dataport < IPPORT_RESERVED || @@ -343,6 +349,13 @@ } if (EMPTYSTR(confdir)) confdir = _DEFAULT_CONFDIR; + if (!EMPTYSTR(passwdfn)) { + passwdfile = fopen(passwdfn, "rt"); + if (passwdfile == NULL) { + syslog(LOG_ERR, "can't open password file (%s): %m", passwdfn); + exit(1); + } + } memset((char *)&his_addr, 0, sizeof(his_addr)); addrlen = sizeof(his_addr.si_su); @@ -498,6 +511,44 @@ } /* + * Get a password entity for a named user from the current password + * file. (Wrapper around fgetpwent to make it behave like getpwnam). + */ +static struct passwd *ftp_getpwnam(const char *name) +{ + struct passwd *p; + + if (EMPTYSTR(passwdfn)) { + /* We're not using a nonstandard password file */ + return getpwnam(name); + } + + /* Make sure it's still open */ + if (passwdfile == NULL) { + syslog(LOG_ERR, "password file (%s) has been closed", passwdfn); + return NULL; + } + + /* Rewind password file */ + if (fseek(passwdfile, 0, SEEK_SET) != 0) { + syslog(LOG_ERR, "can't rewind password file (%s): %m", passwdfn); + return NULL; + } + + /* Read one password entity at a time until we find the + * desired user or hit the end of the file. */ + while ((p = fgetpwent(passwdfile)) != NULL) { + if (strcmp(p->pw_name, name) == 0) { + /* Found the user */ + return p; + } + } + + /* User not found */ + return NULL; +} + +/* * Save the result of a getpwnam. Used for USER command, since * the data returned must not be clobbered by any other command * (e.g., globbing). @@ -508,7 +559,7 @@ static struct passwd save; struct passwd *p; - if ((p = getpwnam(name)) == NULL) + if ((p = ftp_getpwnam(name)) == NULL) return (p); if (save.pw_name) { free((char *)save.pw_name); @@ -1066,6 +1117,12 @@ #if HAVE_SETLOGIN setlogin(pw->pw_name); #endif + if (!EMPTYSTR(passwdfn)) { + /* Close the password file so we can't inadvertently + * read from it while unprivileged */ + fclose(passwdfile); + passwdfile = NULL; + } if (dropprivs || (curclass.type != CLASS_REAL && ntohs(ctrl_addr.su_port) > IPPORT_RESERVED + 1)) { @@ -2897,9 +2954,15 @@ return 1; #if HAVE_GETSPNAM - if ((spw = getspnam(pw->pw_name)) == NULL) - return 1; - orig = spw->sp_pwdp; + if (!EMPTYSTR(passwdfn)) { + /* Using an alternate password file; don't look in the shadow file for password */ + orig = pw->pw_passwd; + } else { + /* Using the ordinary password system */ + if ((spw = getspnam(pw->pw_name)) == NULL) + return 1; + orig = spw->sp_pwdp; + } #else orig = pw->pw_passwd; /* save existing password */ #if HAVE_PW_EXPIRE