Rewrite pam_passwd_conv_proc to be more robust: avoid assuming that the

pam_message array contains exactly one PAM_PROMPT_ECHO_OFF message.
Instead, deal with however many messages there are, and don't throw error
for PAM_ERROR_MSG and PAM_TEXT_INFO messages.  This logic is borrowed from
openssh 5.2p1, which hopefully has seen more real-world PAM usage than we
have.  Per bug #5121 from Ryan Douglas, which turned out to be caused by
the conv_proc being called with zero messages.  Apparently that is normal
behavior given the combination of Linux pam_krb5 with MS Active Directory
as the domain controller.

Patch all the way back, since this code has been essentially untouched
since 7.4.  (Surprising we've not heard complaints before.)
REL8_4_STABLE
Tom Lane 16 years ago
parent a04cb27ce3
commit 8842800d79
  1. 122
      src/backend/libpq/auth.c

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.183.2.2 2009/10/14 22:10:01 heikki Exp $ * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.183.2.3 2009/10/16 22:08:42 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -441,7 +441,6 @@ ClientAuthentication(Port *port)
case uaPAM: case uaPAM:
#ifdef USE_PAM #ifdef USE_PAM
pam_port_cludge = port;
status = CheckPAMAuth(port, port->user_name, ""); status = CheckPAMAuth(port, port->user_name, "");
#else #else
Assert(false); Assert(false);
@ -1880,72 +1879,103 @@ static int
pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg, pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg,
struct pam_response ** resp, void *appdata_ptr) struct pam_response ** resp, void *appdata_ptr)
{ {
if (num_msg != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF) char *passwd;
{ struct pam_response *reply;
switch (msg[0]->msg_style) int i;
{
case PAM_ERROR_MSG:
ereport(LOG,
(errmsg("error from underlying PAM layer: %s",
msg[0]->msg)));
return PAM_CONV_ERR;
default:
ereport(LOG,
(errmsg("unsupported PAM conversation %d/%s",
msg[0]->msg_style, msg[0]->msg)));
return PAM_CONV_ERR;
}
}
if (!appdata_ptr) if (appdata_ptr)
passwd = (char *) appdata_ptr;
else
{ {
/* /*
* Workaround for Solaris 2.6 where the PAM library is broken and does * Workaround for Solaris 2.6 where the PAM library is broken and does
* not pass appdata_ptr to the conversation routine * not pass appdata_ptr to the conversation routine
*/ */
appdata_ptr = pam_passwd; passwd = pam_passwd;
} }
*resp = NULL; /* in case of error exit */
if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG)
return PAM_CONV_ERR;
/* /*
* Password wasn't passed to PAM the first time around - let's go ask the * Explicitly not using palloc here - PAM will free this memory in
* client to send a password, which we then stuff into PAM. * pam_end()
*/ */
if (strlen(appdata_ptr) == 0) if ((reply = calloc(num_msg, sizeof(struct pam_response))) == NULL)
{ {
char *passwd; ereport(LOG,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
return PAM_CONV_ERR;
}
for (i = 0; i < num_msg; i++)
{
switch (msg[i]->msg_style)
{
case PAM_PROMPT_ECHO_OFF:
if (strlen(passwd) == 0)
{
/*
* Password wasn't passed to PAM the first time around -
* let's go ask the client to send a password, which we
* then stuff into PAM.
*/
sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD); sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD);
passwd = recv_password_packet(pam_port_cludge); passwd = recv_password_packet(pam_port_cludge);
if (passwd == NULL) if (passwd == NULL)
return PAM_CONV_ERR; /* client didn't want to send password */ {
/*
* Client didn't want to send password. We
* intentionally do not log anything about this.
*/
goto fail;
}
if (strlen(passwd) == 0) if (strlen(passwd) == 0)
{ {
ereport(LOG, ereport(LOG,
(errmsg("empty password returned by client"))); (errmsg("empty password returned by client")));
return PAM_CONV_ERR; goto fail;
} }
appdata_ptr = passwd;
} }
if ((reply[i].resp = strdup(passwd)) == NULL)
/* goto fail;
* Explicitly not using palloc here - PAM will free this memory in reply[i].resp_retcode = PAM_SUCCESS;
* pam_end() break;
*/ case PAM_ERROR_MSG:
*resp = calloc(num_msg, sizeof(struct pam_response));
if (!*resp)
{
ereport(LOG, ereport(LOG,
(errcode(ERRCODE_OUT_OF_MEMORY), (errmsg("error from underlying PAM layer: %s",
errmsg("out of memory"))); msg[i]->msg)));
return PAM_CONV_ERR; /* FALL THROUGH */
case PAM_TEXT_INFO:
/* we don't bother to log TEXT_INFO messages */
if ((reply[i].resp = strdup("")) == NULL)
goto fail;
reply[i].resp_retcode = PAM_SUCCESS;
break;
default:
elog(LOG, "unsupported PAM conversation %d/\"%s\"",
msg[i]->msg_style,
msg[i]->msg ? msg[i]->msg : "(none)");
goto fail;
}
} }
(*resp)[0].resp = strdup((char *) appdata_ptr); *resp = reply;
(*resp)[0].resp_retcode = 0; return PAM_SUCCESS;
return ((*resp)[0].resp ? PAM_SUCCESS : PAM_CONV_ERR); fail:
/* free up whatever we allocated */
for (i = 0; i < num_msg; i++)
{
if (reply[i].resp != NULL)
free(reply[i].resp);
}
free(reply);
return PAM_CONV_ERR;
} }
@ -1959,10 +1989,12 @@ CheckPAMAuth(Port *port, char *user, char *password)
pam_handle_t *pamh = NULL; pam_handle_t *pamh = NULL;
/* /*
* Apparently, Solaris 2.6 is broken, and needs ugly static variable * We can't entirely rely on PAM to pass through appdata --- it appears
* workaround * not to work on at least Solaris 2.6. So use these ugly static
* variables instead.
*/ */
pam_passwd = password; pam_passwd = password;
pam_port_cludge = port;
/* /*
* Set the application data portion of the conversation struct This is * Set the application data portion of the conversation struct This is

Loading…
Cancel
Save