Reorganize USE_PAM support so that the reply(331,) from USER is

performed by the pam_conv (PAM conversation) callback, which then
getline()s the PASS reply internally.  This involves calling
auth_pam() from user() and caching the result to use later in pass().
This allows the PAM modules to present a different password prompt
dialog if necesary.  For example:
  Name (localhost:lukem):
  331 User lukem accepted, provide password [ otp-md4 89 xxxx12345 ].
versus
  Name (localhost:lukem): root
  331 User root accepted, provide password.

This is independent of (and effectively exclusive to) USE_SKEY support.

Previously ftpd with USE_SKEY=yes would provide the skey prompt
if the user had an skey configured, even if /etc/pam.d/ftpd didn't
have pam_skey in use.
I.e., ftpd shouldn't need special support for custom password prompts
(such as skey) if PAM is in use.
This commit is contained in:
lukem 2008-03-27 08:12:09 +00:00
parent a749c03e28
commit 4f3847e772
3 changed files with 129 additions and 42 deletions

View File

@ -1,4 +1,4 @@
# $NetBSD: Makefile,v 1.57 2006/12/14 20:09:35 he Exp $
# $NetBSD: Makefile,v 1.58 2008/03/27 08:12:09 lukem Exp $
# @(#)Makefile 8.2 (Berkeley) 4/4/94
.include <bsd.own.mk>
@ -21,16 +21,17 @@ CPPFLAGS+=-DINET6
WARNS=2
.if (${USE_PAM} != "no")
CPPFLAGS+=-DUSE_PAM
DPADD+= ${LIBPAM} ${PAM_STATIC_DPADD}
LDADD+= -lpam ${PAM_STATIC_LDADD}
.else # USE_PAM == no
.if (${USE_SKEY} != "no")
CPPFLAGS+=-DSKEY
DPADD+= ${LIBSKEY}
LDADD+= -lskey
.endif
.if (${USE_PAM} != "no")
CPPFLAGS+=-DUSE_PAM
DPADD+= ${LIBPAM} ${PAM_STATIC_DPADD}
LDADD+= -lpam ${PAM_STATIC_LDADD}
.endif
.endif # USE_PAM == no
ftpd.o ftpcmd.o: version.h

View File

@ -1,7 +1,7 @@
/* $NetBSD: ftpd.c,v 1.182 2007/07/23 10:41:05 lukem Exp $ */
/* $NetBSD: ftpd.c,v 1.183 2008/03/27 08:12:09 lukem Exp $ */
/*
* Copyright (c) 1997-2004 The NetBSD Foundation, Inc.
* Copyright (c) 1997-2008 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
@ -105,7 +105,7 @@ __COPYRIGHT(
#if 0
static char sccsid[] = "@(#)ftpd.c 8.5 (Berkeley) 4/28/95";
#else
__RCSID("$NetBSD: ftpd.c,v 1.182 2007/07/23 10:41:05 lukem Exp $");
__RCSID("$NetBSD: ftpd.c,v 1.183 2008/03/27 08:12:09 lukem Exp $");
#endif
#endif /* not lint */
@ -200,7 +200,7 @@ off_t byte_count;
static char ttyline[20];
#ifdef USE_PAM
static int auth_pam(struct passwd **, const char *);
static int auth_pam(void);
pam_handle_t *pamh = NULL;
#endif
@ -250,6 +250,7 @@ static int bind_pasv_addr(void);
static int checkuser(const char *, const char *, int, int, char **);
static int checkaccess(const char *);
static int checkpassword(const struct passwd *, const char *);
static void do_pass(int, int, const char *);
static void end_login(void);
static FILE *getdatasock(const char *);
static char *gunique(const char *);
@ -848,6 +849,9 @@ user(const char *name)
#ifdef LOGIN_CAP
login_cap_t *lc = NULL;
#endif
#ifdef USE_PAM
int e;
#endif
class = NULL;
if (logged_in) {
@ -927,6 +931,7 @@ user(const char *name)
}
curclass.type = CLASS_CHROOT;
}
/* determine default class */
if (class == NULL) {
switch (curclass.type) {
@ -976,6 +981,11 @@ user(const char *name)
/* if haven't asked yet (i.e, not anon), ask now */
if (!askpasswd) {
askpasswd = 1;
#ifdef USE_PAM
e = auth_pam(); /* this does reply(331, ...) */
do_pass(1, e, "");
goto cleanup_user;
#else /* !USE_PAM */
#ifdef SKEY
if (skey_haskey(curname) == 0) {
const char *myskey;
@ -987,6 +997,7 @@ user(const char *name)
} else
#endif
reply(331, "Password required for %s.", curname);
#endif /* !USE_PAM */
}
cleanup_user:
@ -1270,6 +1281,22 @@ end_login(void)
void
pass(const char *passwd)
{
do_pass(0, 0, passwd);
}
/*
* Perform the passwd confirmation and login.
*
* If pass_checked is zero, confirm passwd is correct, & ignore pass_rval.
* This is the traditional PASS implementation.
*
* If pass_checked is non-zero, use pass_rval and ignore passwd.
* This is used by auth_pam() which has already parsed PASS.
* This only applies to curclass.type != CLASS_GUEST.
*/
static void
do_pass(int pass_checked, int pass_rval, const char *passwd)
{
int rval;
char root[MAXPATHLEN];
@ -1279,6 +1306,9 @@ pass(const char *passwd)
#ifdef USE_PAM
int e;
#endif
rval = 1;
if (logged_in || askpasswd == 0) {
reply(503, "Login with USER first.");
return;
@ -1290,18 +1320,15 @@ pass(const char *passwd)
rval = 1; /* failure below */
goto skip;
}
#ifdef USE_PAM
rval = auth_pam(&pw, passwd);
#ifdef notdef
/* If PAM fails, we proceed with other authentications */
if (rval >= 0) {
if (pass_checked) { /* password validated in user() */
rval = pass_rval;
goto skip;
}
#else
/* If PAM fails, that's it */
#ifdef USE_PAM
syslog(LOG_ERR, "do_pass: USE_PAM shouldn't get here");
rval = 1;
goto skip;
#endif
#endif
#if defined(KERBEROS)
if (klogin(pw, "", hostname, (char *)passwd) == 0) {
rval = 0;
@ -2180,7 +2207,7 @@ send_data_with_mmap(int filefd, int netfd, const struct stat *st, int isdata)
}
/*
* Tranfer the contents of "instr" to "outstr" peer using the appropriate
* Transfer the contents of "instr" to "outstr" peer using the appropriate
* encapsulation of the data subject to Mode, Structure, and Type.
*
* NB: Form isn't handled.
@ -3658,11 +3685,9 @@ cprintf(FILE *fd, const char *fmt, ...)
* the following code is stolen from imap-uw PAM authentication module and
* login.c
*/
#define COPY_STRING(s) (s ? strdup(s) : NULL)
typedef struct {
const char *uname; /* user name */
const char *pass; /* password */
const char *uname; /* user name */
int triedonce; /* if non-zero, tried before */
} ftpd_cred_t;
static int
@ -3670,38 +3695,90 @@ auth_conv(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata)
{
int i;
size_t n;
ftpd_cred_t *cred = (ftpd_cred_t *) appdata;
struct pam_response *myreply;
char pbuf[FTP_BUFLEN];
if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG)
return (PAM_CONV_ERR);
myreply = calloc(num_msg, sizeof *myreply);
if (myreply == NULL)
return PAM_BUF_ERR;
for (i = 0; i < num_msg; i++) {
myreply[i].resp_retcode = 0;
myreply[i].resp = NULL;
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_ON: /* assume want user name */
myreply[i].resp_retcode = PAM_SUCCESS;
myreply[i].resp = COPY_STRING(cred->uname);
case PAM_PROMPT_ECHO_ON: /* user */
myreply[i].resp = ftpd_strdup(cred->uname);
/* PAM frees resp. */
break;
case PAM_PROMPT_ECHO_OFF: /* assume want password */
myreply[i].resp_retcode = PAM_SUCCESS;
myreply[i].resp = COPY_STRING(cred->pass);
/* PAM frees resp. */
case PAM_PROMPT_ECHO_OFF: /* authtok (password) */
/*
* Only send a single 331 reply and
* then expect a PASS.
*/
if (cred->triedonce) {
syslog(LOG_ERR,
"auth_conv: already performed PAM_PROMPT_ECHO_OFF");
goto fail;
}
cred->triedonce++;
if (msg[i]->msg[0] == '\0') {
(void)strlcpy(pbuf, "password", sizeof(pbuf));
} else {
/* Uncapitalize msg, remove trailing ':' */
(void)strlcpy(pbuf, msg[i]->msg, sizeof(pbuf));
n = strlen(pbuf);
if (isupper((unsigned char)pbuf[0]))
pbuf[0] = tolower(
(unsigned char)pbuf[0]);
n = strlen(pbuf) - 1;
if (pbuf[n] == ':')
pbuf[n] = '\0';
}
/* Send reply, wait for a response. */
reply(331, "User %s accepted, provide %s.",
cred->uname, pbuf);
(void) alarm(curclass.timeout);
if (getline(pbuf, sizeof(pbuf)-1, stdin) == NULL) {
reply(221, "You could at least say goodbye.");
dologout(0);
}
(void) alarm(0);
/* Ensure it is PASS */
if (strncasecmp(pbuf, "PASS ", 5) != 0) {
syslog(LOG_ERR,
"auth_conv: unexpected reply '%.4s'", pbuf);
/* XXX: should we do this reply(-530, ..) ? */
reply(-530, "Unexpected reply '%.4s'.", pbuf);
goto fail;
}
/* Strip CRLF from "PASS" reply */
n = strlen(pbuf);
while (--n >= 5 &&
(pbuf[n] == '\r' || pbuf[n] == '\n'))
pbuf[n] = '\0';
/* Copy password into reply */
myreply[i].resp = ftpd_strdup(pbuf+5);
/* PAM frees resp. */
break;
case PAM_TEXT_INFO:
case PAM_ERROR_MSG:
myreply[i].resp_retcode = PAM_SUCCESS;
myreply[i].resp = NULL;
break;
default: /* unknown message style */
free(myreply);
return PAM_CONV_ERR;
goto fail;
}
}
*resp = myreply;
return PAM_SUCCESS;
fail:
free(myreply);
*resp = NULL;
return PAM_CONV_ERR;
}
/*
@ -3710,18 +3787,19 @@ auth_conv(int num_msg, const struct pam_message **msg,
* error occurs (e.g., the "/etc/pam.conf" file is missing) then this
* function returns -1. This can be used as an indication that we should
* fall back to a different authentication mechanism.
* pw maybe be updated to a new user if PAM_USER changes from curname.
*/
static int
auth_pam(struct passwd **ppw, const char *pwstr)
auth_pam(void)
{
const char *tmpl_user;
const void *item;
int rval;
int e;
ftpd_cred_t auth_cred = { (*ppw)->pw_name, pwstr };
ftpd_cred_t auth_cred = { curname, 0 };
struct pam_conv conv = { &auth_conv, &auth_cred };
e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh);
e = pam_start("ftpd", curname, &conv, &pamh);
if (e != PAM_SUCCESS) {
/*
* In OpenPAM, it's OK to pass NULL to pam_strerror()
@ -3776,8 +3854,16 @@ auth_pam(struct passwd **ppw, const char *pwstr)
if ((e = pam_get_item(pamh, PAM_USER, &item)) ==
PAM_SUCCESS) {
tmpl_user = (const char *) item;
if (strcmp((*ppw)->pw_name, tmpl_user) != 0)
*ppw = sgetpwnam(tmpl_user);
if (pw == NULL
|| strcmp(pw->pw_name, tmpl_user) != 0) {
pw = sgetpwnam(tmpl_user);
if (ftpd_debug)
syslog(LOG_DEBUG,
"PAM changed user from %s to %s",
curname, pw->pw_name);
(void)strlcpy(curname, pw->pw_name,
curname_len);
}
} else
syslog(LOG_ERR, "Couldn't get PAM_USER: %s",
pam_strerror(pamh, e));

View File

@ -1,6 +1,6 @@
/* $NetBSD: version.h,v 1.65 2006/09/25 06:30:55 lukem Exp $ */
/* $NetBSD: version.h,v 1.66 2008/03/27 08:12:09 lukem Exp $ */
/*-
* Copyright (c) 1999-2004 The NetBSD Foundation, Inc.
* Copyright (c) 1999-2008 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
@ -36,5 +36,5 @@
*/
#ifndef FTPD_VERSION
#define FTPD_VERSION "NetBSD-ftpd 20060923"
#define FTPD_VERSION "NetBSD-ftpd 20080326"
#endif