diff --git a/configure.in b/configure.in index cb3d9bd9e8..b1fd48218a 100644 --- a/configure.in +++ b/configure.in @@ -432,7 +432,6 @@ PGAC_ARG_BOOL(with, perl, no, [ --with-perl build Perl interface an AC_MSG_RESULT([$with_perl]) AC_SUBST(with_perl) - # # Optionally build Python interface module # @@ -529,6 +528,23 @@ AC_DEFINE_UNQUOTED([PG_KRB_SRVNAM], ["$with_krb_srvnam"], [The name of the PostgreSQL service principal in Kerberos]) +# +# PAM +# +AC_MSG_CHECKING([whether to build with PAM support]) +PGAC_ARG_OPTARG(with, pam, + [ --with-pam[=DIR] build with PAM support [/usr]], + [pam_prefix=/usr], + [pam_prefix=$withval], +[ + AC_MSG_RESULT([yes]) + AC_DEFINE([USE_PAM], 1, [Define to build with PAM support]) + +], +[AC_MSG_RESULT(no)]) + +AC_SUBST(with_pam) + # # OpenSSL @@ -752,11 +768,14 @@ if test "$with_openssl" = yes ; then AC_CHECK_LIB(ssl, [SSL_library_init], [], [AC_MSG_ERROR([library 'ssl' is required for OpenSSL])]) fi +if test "$with_pam" = yes ; then + AC_CHECK_LIB(pam, [pam_start], [], [AC_MSG_ERROR([library 'pam' is required for PAM])]) +fi + if test "$enable_nls" = yes ; then PGAC_CHECK_GETTEXT fi - ## ## Header files ## @@ -794,6 +813,10 @@ if test "$with_openssl" = yes ; then AC_CHECK_HEADER([openssl/err.h], [], [AC_MSG_ERROR([header file is required for OpenSSL])]) fi +if test "$with_pam" = yes ; then + AC_CHECK_HEADER([security/pam_appl.h], [], [AC_MSG_ERROR([header file is required for PAM])]) +fi + ## ## Types, structures, compiler characteristics diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 76cba40751..86aab400f7 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1,4 +1,4 @@ - + Client Authentication @@ -278,6 +278,27 @@ hostssl database IP-address + + + pam + + + This authentication type operates similar to + password, with the main difference that + it will use PAM (Pluggable Authentication Modules) as the + authentication mechanism. The authentication + option following the pam keyword + specifies the service name that will be passed to PAM. The + default service name is postgresql. + For more information about PAM, please read Linux-PAM + Page and Solaris-PAM + Page. + + + + diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index adcb881c1c..56c1d01c5c 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.64 2001/08/21 15:21:25 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.65 2001/09/06 03:23:38 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -43,6 +43,24 @@ static int recv_and_check_passwordv0(Port *port); char *pg_krb_server_keyfile; +#ifdef USE_PAM +#include + +#define PGSQL_PAM_SERVICE "postgresql" /* Service name passed to PAM */ + +static int CheckPAMAuth(Port *port, char *user, char *password); +static int pam_passwd_conv_proc(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr); + +static struct pam_conv pam_passw_conv = { + &pam_passwd_conv_proc, + NULL +}; + +static char * pam_passwd = NULL; /* Workaround for Solaris 2.6 brokenness */ +static Port * pam_port_cludge; /* Workaround for passing "Port + * *port" into pam_passwd_conv_proc */ +#endif /* USE_PAM */ #ifdef KRB4 /*---------------------------------------------------------------- @@ -428,6 +446,11 @@ auth_failed(Port *port) case uaPassword: authmethod = "Password"; break; +#ifdef USE_PAM + case uaPAM: + authmethod = "PAM"; + break; +#endif /* USE_PAM */ } elog(FATAL, "%s authentication failed for user \"%s\"", @@ -525,15 +548,21 @@ ClientAuthentication(Port *port) status = recv_and_check_password_packet(port); break; - case uaCrypt: - sendAuthRequest(port, AUTH_REQ_CRYPT); - status = recv_and_check_password_packet(port); - break; - - case uaPassword: - sendAuthRequest(port, AUTH_REQ_PASSWORD); - status = recv_and_check_password_packet(port); + case uaCrypt: + sendAuthRequest(port, AUTH_REQ_CRYPT); + status = recv_and_check_password_packet(port); + break; + + case uaPassword: + sendAuthRequest(port, AUTH_REQ_PASSWORD); + status = recv_and_check_password_packet(port); + break; +#ifdef USE_PAM + case uaPAM: + pam_port_cludge = port; + status = CheckPAMAuth(port, port->user, ""); break; +#endif /* USE_PAM */ case uaTrust: status = STATUS_OK; @@ -577,7 +606,190 @@ sendAuthRequest(Port *port, AuthRequest areq) pq_flush(); } +#ifdef USE_PAM +/* + * PAM conversation function + */ + +static int +pam_passwd_conv_proc (int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) +{ + StringInfoData buf; + int32 len; + + if (num_msg != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF) { + switch(msg[0]->msg_style) { + case PAM_ERROR_MSG: + snprintf(PQerrormsg, PQERRORMSG_LENGTH, + "pam_passwd_conv_proc: Error from underlying PAM layer: '%s'\n", msg[0]->msg); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); + return PAM_CONV_ERR; + default: + snprintf(PQerrormsg, PQERRORMSG_LENGTH, + "pam_passwd_conv_proc: Unexpected PAM conversation %d/'%s'\n", + msg[0]->msg_style, msg[0]->msg); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); + return PAM_CONV_ERR; + } + } + + if (!appdata_ptr) { + /* Workaround for Solaris 2.6 where the PAM library is broken + * and does not pass appdata_ptr to the conversation routine + */ + appdata_ptr = pam_passwd; + } + + /* 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. + */ + if(strlen(appdata_ptr) == 0) { + sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD); + if (pq_eof() == EOF || pq_getint(&len, 4) == EOF) { + return PAM_CONV_ERR; /* client didn't want to send password */ + } + + initStringInfo(&buf); + pq_getstr(&buf); + if (DebugLvl) + fprintf(stderr, "received PAM packet with len=%d, pw=%s\n", + len, buf.data); + + if(strlen(buf.data) == 0) { + snprintf(PQerrormsg, PQERRORMSG_LENGTH, "pam_passwd_conv_proc: no password\n"); + fputs(PQerrormsg, stderr); + return PAM_CONV_ERR; + } + appdata_ptr = buf.data; + } + + /* Explicitly not using palloc here - PAM will free this memory in + * pam_end() + */ + *resp = calloc(num_msg, sizeof(struct pam_response)); + if (!*resp) { + snprintf(PQerrormsg, PQERRORMSG_LENGTH, "pam_passwd_conv_proc: Out of memory!\n"); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); + if(buf.data) + pfree(buf.data); + return PAM_CONV_ERR; + } + + (*resp)[0].resp = strdup((char *) appdata_ptr); + (*resp)[0].resp_retcode = 0; + + return ((*resp)[0].resp ? PAM_SUCCESS : PAM_CONV_ERR); +} + + +/* + * Check authentication against PAM. + */ +static int +CheckPAMAuth(Port *port, char *user, char *password) +{ + int retval; + pam_handle_t *pamh = NULL; + + /* + * Apparently, Solaris 2.6 is broken, and needs ugly static + * variable workaround + */ + pam_passwd = password; + + /* Set the application data portion of the conversation struct + * This is later used inside the PAM conversation to pass the + * password to the authentication module. + */ + pam_passw_conv.appdata_ptr = (char*) password; /* from password above, not allocated */ + + /* Optionally, one can set the service name in pg_hba.conf */ + if(port->auth_arg[0] == '\0') { + retval = pam_start(PGSQL_PAM_SERVICE, "pgsql@", &pam_passw_conv, &pamh); + } else { + retval = pam_start(port->auth_arg, "pgsql@", &pam_passw_conv, &pamh); + } + + if (retval != PAM_SUCCESS) { + snprintf(PQerrormsg, PQERRORMSG_LENGTH, + "CheckPAMAuth: Failed to create PAM authenticator: '%s'\n", + pam_strerror(pamh, retval)); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); + pam_passwd = NULL; /* Unset pam_passwd */ + return STATUS_ERROR; + } + + if (retval == PAM_SUCCESS) { + retval = pam_set_item(pamh, PAM_USER, user); + } else { + snprintf(PQerrormsg, PQERRORMSG_LENGTH, + "CheckPAMAuth: pam_set_item(PAM_USER) failed: '%s'\n", + pam_strerror(pamh, retval)); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); + pam_passwd = NULL; /* Unset pam_passwd */ + return STATUS_ERROR; + } + if (retval == PAM_SUCCESS) { + retval = pam_set_item(pamh, PAM_CONV, &pam_passw_conv); + } else { + snprintf(PQerrormsg, PQERRORMSG_LENGTH, + "CheckPAMAuth: pam_set_item(PAM_CONV) failed: '%s'\n", + pam_strerror(pamh, retval)); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); + pam_passwd = NULL; /* Unset pam_passwd */ + return STATUS_ERROR; + } + if (retval == PAM_SUCCESS) { + retval = pam_authenticate(pamh, 0); + } else { + snprintf(PQerrormsg, PQERRORMSG_LENGTH, + "CheckPAMAuth: pam_authenticate failed: '%s'\n", + pam_strerror(pamh, retval)); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); + pam_passwd = NULL; /* Unset pam_passwd */ + return STATUS_ERROR; + } + if (retval == PAM_SUCCESS) { + retval = pam_acct_mgmt(pamh, 0); + } else { + snprintf(PQerrormsg, PQERRORMSG_LENGTH, + "CheckPAMAuth: pam_acct_mgmt failed: '%s'\n", + pam_strerror(pamh, retval)); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); + pam_passwd = NULL; /* Unset pam_passwd */ + return STATUS_ERROR; + } + if (retval == PAM_SUCCESS) { + retval = pam_end(pamh, retval); + if(retval != PAM_SUCCESS) { + snprintf(PQerrormsg, PQERRORMSG_LENGTH, + "CheckPAMAuth: Failed to release PAM authenticator: '%s'\n", + pam_strerror(pamh, retval)); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); + } + + pam_passwd = NULL; /* Unset pam_passwd */ + + return (retval == PAM_SUCCESS ? STATUS_OK : STATUS_ERROR); + } else { + return STATUS_ERROR; + } +} + + + +#endif /* USE_PAM */ /* * Called when we have received the password packet. @@ -670,6 +882,9 @@ map_old_to_new(Port *port, UserAuth old, int status) case uaMD5: case uaCrypt: case uaReject: +#ifdef USE_PAM + case uaPAM: +#endif /* USE_PAM */ status = STATUS_ERROR; break; diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 9dddb61754..69f88c257f 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/libpq/hba.c,v 1.68 2001/08/21 15:49:17 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/hba.c,v 1.69 2001/09/06 03:23:38 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -235,6 +235,10 @@ parse_hba_auth(List *line, ProtocolVersion proto, UserAuth *userauth_p, *userauth_p = uaMD5; else if (strcmp(token, "crypt") == 0) *userauth_p = uaCrypt; +#ifdef USE_PAM + else if (strcmp(token, "pam") == 0) + *userauth_p = uaPAM; +#endif else *error_p = true; line = lnext(line); @@ -277,7 +281,6 @@ parse_hba(List *line, hbaPort *port, bool *found_p, bool *error_p) line_number = lfirsti(line); line = lnext(line); Assert(line != NIL); - /* Check the record type. */ token = lfirst(line); if (strcmp(token, "local") == 0) diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample index c348b7668c..0aff0f43fc 100644 --- a/src/backend/libpq/pg_hba.conf.sample +++ b/src/backend/libpq/pg_hba.conf.sample @@ -148,7 +148,12 @@ # that are part of a network specified later in the file. # To be effective, "reject" must appear before the later # entries. -# +# +# pam: Authentication is passed off to PAM (PostgreSQL must be +# configured --with-pam), using the default service name +# "postgresql" - you can specify your own service name, by +# setting AUTH_ARGUMENT to the desired service name. +# # # # Examples diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 02eee16d9a..6525d5ecb3 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -4,7 +4,7 @@ * Interface to hba.c * * - * $Id: hba.h,v 1.25 2001/08/24 16:59:10 momjian Exp $ + * $Id: hba.h,v 1.26 2001/09/06 03:23:38 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -31,6 +31,9 @@ typedef enum UserAuth { +#ifdef USE_PAM + uaPAM, +#endif /* USE_PAM */ uaReject, uaKrb4, uaKrb5, diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 8a165be62d..30a3834261 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -8,7 +8,7 @@ * or in pg_config.h afterwards. Of course, if you edit pg_config.h, then your * changes will be overwritten the next time you run configure. * - * $Id: pg_config.h.in,v 1.2 2001/09/06 02:56:32 momjian Exp $ + * $Id: pg_config.h.in,v 1.3 2001/09/06 03:23:38 momjian Exp $ */ #ifndef PG_CONFIG_H @@ -63,6 +63,9 @@ /* Define to build with (Open)SSL support (--with-openssl[=DIR]) */ #undef USE_SSL +/* Define to build with PAM Support */ +#undef USE_PAM + /* * DEF_PGPORT is the TCP port number on which the Postmaster listens and * which clients will try to connect to. This is just a default value;