Tuesday, September 30, 2008

[HACKERS] pg_hba options parsing

*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 126,134 **** char *pg_krb_realm = NULL;
* MIT Kerberos authentication system - protocol version 5
*----------------------------------------------------------------
*/
- static int pg_krb5_recvauth(Port *port);
-
#ifdef KRB5

#include <krb5.h>
/* Some old versions of Kerberos do not include <com_err.h> in <krb5.h> */
--- 126,133 ----
* MIT Kerberos authentication system - protocol version 5
*----------------------------------------------------------------
*/
#ifdef KRB5
+ static int pg_krb5_recvauth(Port *port);

#include <krb5.h>
/* Some old versions of Kerberos do not include <com_err.h> in <krb5.h> */
***************
*** 150,163 **** static krb5_principal pg_krb5_server;
* GSSAPI Authentication
*----------------------------------------------------------------
*/
- static int pg_GSS_recvauth(Port *port);
-
#ifdef ENABLE_GSS
#if defined(HAVE_GSSAPI_H)
#include <gssapi.h>
#else
#include <gssapi/gssapi.h>
#endif
#endif /* ENABLE_GSS */


--- 149,162 ----
* GSSAPI Authentication
*----------------------------------------------------------------
*/
#ifdef ENABLE_GSS
#if defined(HAVE_GSSAPI_H)
#include <gssapi.h>
#else
#include <gssapi/gssapi.h>
#endif
+
+ static int pg_GSS_recvauth(Port *port);
#endif /* ENABLE_GSS */


***************
*** 165,176 **** static int pg_GSS_recvauth(Port *port);
* SSPI Authentication
*----------------------------------------------------------------
*/
- static int pg_SSPI_recvauth(Port *port);
-
#ifdef ENABLE_SSPI
typedef SECURITY_STATUS
(WINAPI * QUERY_SECURITY_CONTEXT_TOKEN_FN) (
PCtxtHandle, void **);
#endif


--- 164,174 ----
* SSPI Authentication
*----------------------------------------------------------------
*/
#ifdef ENABLE_SSPI
typedef SECURITY_STATUS
(WINAPI * QUERY_SECURITY_CONTEXT_TOKEN_FN) (
PCtxtHandle, void **);
+ static int pg_SSPI_recvauth(Port *port);
#endif


***************
*** 236,251 **** auth_failed(Port *port, int status)
case uaPassword:
errstr = gettext_noop("password authentication failed for user \"%s\"");
break;
- #ifdef USE_PAM
case uaPAM:
errstr = gettext_noop("PAM authentication failed for user \"%s\"");
break;
- #endif /* USE_PAM */
- #ifdef USE_LDAP
case uaLDAP:
errstr = gettext_noop("LDAP authentication failed for user \"%s\"");
break;
- #endif /* USE_LDAP */
default:
errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
break;
--- 234,245 ----
***************
*** 316,333 **** ClientAuthentication(Port *port)
--- 310,339 ----
}

case uaKrb5:
+ #ifdef KRB5
sendAuthRequest(port, AUTH_REQ_KRB5);
status = pg_krb5_recvauth(port);
+ #else
+ Assert(false);
+ #endif
break;

case uaGSS:
+ #ifdef ENABLE_GSS
sendAuthRequest(port, AUTH_REQ_GSS);
status = pg_GSS_recvauth(port);
+ #else
+ Assert(false);
+ #endif
break;

case uaSSPI:
+ #ifdef ENABLE_SSPI
sendAuthRequest(port, AUTH_REQ_SSPI);
status = pg_SSPI_recvauth(port);
+ #else
+ Assert(false);
+ #endif
break;

case uaIdent:
***************
*** 377,393 **** ClientAuthentication(Port *port)
status = recv_and_check_password_packet(port);
break;

- #ifdef USE_PAM
case uaPAM:
pam_port_cludge = port;
status = CheckPAMAuth(port, port->user_name, "");
break;
#endif /* USE_PAM */

- #ifdef USE_LDAP
case uaLDAP:
status = CheckLDAPAuth(port);
break;
#endif

case uaTrust:
--- 383,403 ----
status = recv_and_check_password_packet(port);
break;

case uaPAM:
+ #ifdef USE_PAM
pam_port_cludge = port;
status = CheckPAMAuth(port, port->user_name, "");
+ #else
+ Assert(false);
break;
#endif /* USE_PAM */

case uaLDAP:
+ #ifdef USE_LDAP
status = CheckLDAPAuth(port);
break;
+ #else
+ Assert(false);
#endif

case uaTrust:
***************
*** 713,731 **** pg_krb5_recvauth(Port *port)
return STATUS_ERROR;
}

! if (pg_krb_caseins_users)
! ret = pg_strncasecmp(port->user_name, kusername, SM_DATABASE_USER);
! else
! ret = strncmp(port->user_name, kusername, SM_DATABASE_USER);
! if (ret)
! {
! ereport(LOG,
! (errmsg("unexpected Kerberos user name received from client (received \"%s\", expected \"%s\")",
! port->user_name, kusername)));
! ret = STATUS_ERROR;
! }
! else
! ret = STATUS_OK;

krb5_free_ticket(pg_krb5_context, ticket);
krb5_auth_con_free(pg_krb5_context, auth_context);
--- 723,730 ----
return STATUS_ERROR;
}

! ret = check_usermap(port->hba->usermap, port->user_name, kusername,
! pg_krb_caseins_users);

krb5_free_ticket(pg_krb5_context, ticket);
krb5_auth_con_free(pg_krb5_context, auth_context);
***************
*** 733,748 **** pg_krb5_recvauth(Port *port)

return ret;
}
- #else
-
- static int
- pg_krb5_recvauth(Port *port)
- {
- ereport(LOG,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("Kerberos 5 not implemented on this server")));
- return STATUS_ERROR;
- }
#endif /* KRB5 */


--- 732,737 ----
***************
*** 1020,1057 **** pg_GSS_recvauth(Port *port)
return STATUS_ERROR;
}

! if (pg_krb_caseins_users)
! ret = pg_strcasecmp(port->user_name, gbuf.value);
! else
! ret = strcmp(port->user_name, gbuf.value);
!
! if (ret)
! {
! /* GSS name and PGUSER are not equivalent */
! elog(DEBUG2,
! "provided username (%s) and GSSAPI username (%s) don't match",
! port->user_name, (char *) gbuf.value);
!
! gss_release_buffer(&lmin_s, &gbuf);
! return STATUS_ERROR;
! }

gss_release_buffer(&lmin_s, &gbuf);

return STATUS_OK;
}
-
- #else /* no ENABLE_GSS */
-
- static int
- pg_GSS_recvauth(Port *port)
- {
- ereport(LOG,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("GSSAPI not implemented on this server")));
- return STATUS_ERROR;
- }
-
#endif /* ENABLE_GSS */


--- 1009,1021 ----
return STATUS_ERROR;
}

! ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value,
! pg_krb_caseins_users);

gss_release_buffer(&lmin_s, &gbuf);

return STATUS_OK;
}
#endif /* ENABLE_GSS */


***************
*** 1328,1357 **** pg_SSPI_recvauth(Port *port)
* We have the username (without domain/realm) in accountname, compare to
* the supplied value. In SSPI, always compare case insensitive.
*/
! if (pg_strcasecmp(port->user_name, accountname))
! {
! /* GSS name and PGUSER are not equivalent */
! elog(DEBUG2,
! "provided username (%s) and SSPI username (%s) don't match",
! port->user_name, accountname);
!
! return STATUS_ERROR;
! }
!
! return STATUS_OK;
}
-
- #else /* no ENABLE_SSPI */
-
- static int
- pg_SSPI_recvauth(Port *port)
- {
- ereport(LOG,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SSPI not implemented on this server")));
- return STATUS_ERROR;
- }
-
#endif /* ENABLE_SSPI */


--- 1292,1299 ----
* We have the username (without domain/realm) in accountname, compare to
* the supplied value. In SSPI, always compare case insensitive.
*/
! return check_usermap(port->hba->usermap, port->user_name, accountname, true);
}
#endif /* ENABLE_SSPI */


***************
*** 1795,1808 **** authident(hbaPort *port)
return STATUS_ERROR;
}

! ereport(DEBUG2,
! (errmsg("Ident protocol identifies remote user as \"%s\"",
! ident_user)));
!
! if (check_ident_usermap(port->hba->usermap, port->user_name, ident_user))
! return STATUS_OK;
! else
! return STATUS_ERROR;
}


--- 1737,1743 ----
return STATUS_ERROR;
}

! return check_usermap(port->hba->usermap, port->user_name, ident_user, false);
}


***************
*** 1913,1920 **** CheckPAMAuth(Port *port, char *user, char *password)
* not allocated */

/* Optionally, one can set the service name in pg_hba.conf */
! if (port->hba->auth_arg && port->hba->auth_arg[0] != '\0')
! retval = pam_start(port->hba->auth_arg, "pgsql@",
&pam_passw_conv, &pamh);
else
retval = pam_start(PGSQL_PAM_SERVICE, "pgsql@",
--- 1848,1855 ----
* not allocated */

/* Optionally, one can set the service name in pg_hba.conf */
! if (port->hba->pamservice && port->hba->pamservice[0] != '\0')
! retval = pam_start(port->hba->pamservice, "pgsql@",
&pam_passw_conv, &pamh);
else
retval = pam_start(PGSQL_PAM_SERVICE, "pgsql@",
***************
*** 2000,2075 **** static int
CheckLDAPAuth(Port *port)
{
char *passwd;
- char server[128];
- char basedn[128];
- char prefix[128];
- char suffix[128];
LDAP *ldap;
- bool ssl = false;
int r;
int ldapversion = LDAP_VERSION3;
- int ldapport = LDAP_PORT;
char fulluser[NAMEDATALEN + 256 + 1];

! if (!port->hba->auth_arg || port->hba->auth_arg[0] == '\0')
{
ereport(LOG,
! (errmsg("LDAP configuration URL not specified")));
return STATUS_ERROR;
}

! /*
! * Crack the LDAP url. We do a very trivial parse:
! *
! * ldap[s]://<server>[:<port>]/<basedn>[;prefix[;suffix]]
! *
! * This code originally used "%127s" for the suffix, but that doesn't
! * work for embedded whitespace. We know that tokens formed by
! * hba.c won't include newlines, so we can use a "not newline" scanset
! * instead.
! */
!
! server[0] = '\0';
! basedn[0] = '\0';
! prefix[0] = '\0';
! suffix[0] = '\0';
!
! /* ldap, including port number */
! r = sscanf(port->hba->auth_arg,
! "ldap://%127[^:]:%d/%127[^;];%127[^;];%127[^\n]",
! server, &ldapport, basedn, prefix, suffix);
! if (r < 3)
! {
! /* ldaps, including port number */
! r = sscanf(port->hba->auth_arg,
! "ldaps://%127[^:]:%d/%127[^;];%127[^;];%127[^\n]",
! server, &ldapport, basedn, prefix, suffix);
! if (r >= 3)
! ssl = true;
! }
! if (r < 3)
! {
! /* ldap, no port number */
! r = sscanf(port->hba->auth_arg,
! "ldap://%127[^/]/%127[^;];%127[^;];%127[^\n]",
! server, basedn, prefix, suffix);
! }
! if (r < 2)
! {
! /* ldaps, no port number */
! r = sscanf(port->hba->auth_arg,
! "ldaps://%127[^/]/%127[^;];%127[^;];%127[^\n]",
! server, basedn, prefix, suffix);
! if (r >= 2)
! ssl = true;
! }
! if (r < 2)
! {
! ereport(LOG,
! (errmsg("invalid LDAP URL: \"%s\"",
! port->hba->auth_arg)));
! return STATUS_ERROR;
! }

sendAuthRequest(port, AUTH_REQ_PASSWORD);

--- 1935,1954 ----
CheckLDAPAuth(Port *port)
{
char *passwd;
LDAP *ldap;
int r;
int ldapversion = LDAP_VERSION3;
char fulluser[NAMEDATALEN + 256 + 1];

! if (!port->hba->ldapserver|| port->hba->ldapserver[0] == '\0')
{
ereport(LOG,
! (errmsg("LDAP server not specified")));
return STATUS_ERROR;
}

! if (port->hba->ldapport == 0)
! port->hba->ldapport = LDAP_PORT;

sendAuthRequest(port, AUTH_REQ_PASSWORD);

***************
*** 2077,2083 **** CheckLDAPAuth(Port *port)
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */

! ldap = ldap_init(server, ldapport);
if (!ldap)
{
#ifndef WIN32
--- 1956,1962 ----
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */

! ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
if (!ldap)
{
#ifndef WIN32
***************
*** 2100,2106 **** CheckLDAPAuth(Port *port)
return STATUS_ERROR;
}

! if (ssl)
{
#ifndef WIN32
if ((r = ldap_start_tls_s(ldap, NULL, NULL)) != LDAP_SUCCESS)
--- 1979,1985 ----
return STATUS_ERROR;
}

! if (port->hba->ldaptls)
{
#ifndef WIN32
if ((r = ldap_start_tls_s(ldap, NULL, NULL)) != LDAP_SUCCESS)
***************
*** 2155,2161 **** CheckLDAPAuth(Port *port)
}

snprintf(fulluser, sizeof(fulluser), "%s%s%s",
! prefix, port->user_name, suffix);
fulluser[sizeof(fulluser) - 1] = '\0';

r = ldap_simple_bind_s(ldap, fulluser, passwd);
--- 2034,2042 ----
}

snprintf(fulluser, sizeof(fulluser), "%s%s%s",
! port->hba->ldapprefix?port->hba->ldapprefix:"",
! port->user_name,
! port->hba->ldapsuffix?port->hba->ldapsuffix:"");
fulluser[sizeof(fulluser) - 1] = '\0';

r = ldap_simple_bind_s(ldap, fulluser, passwd);
***************
*** 2165,2171 **** CheckLDAPAuth(Port *port)
{
ereport(LOG,
(errmsg("LDAP login failed for user \"%s\" on server \"%s\": error code %d",
! fulluser, server, r)));
return STATUS_ERROR;
}

--- 2046,2052 ----
{
ereport(LOG,
(errmsg("LDAP login failed for user \"%s\" on server \"%s\": error code %d",
! fulluser, port->hba->ldapserver, r)));
return STATUS_ERROR;
}

*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 565,570 **** check_db(const char *dbname, const char *role, char *param_str)
--- 565,606 ----


/*
+ * Macros used to check and report on invalid configuration options.
+ * INVALID_AUTH_OPTION = reports when an option is specified for a method where it's
+ * not supported.
+ * REQUIRE_AUTH_OPTION = same as INVALID_AUTH_OPTION, except it also checks if the
+ * method is actually the one specified. Used as a shortcut when
+ * the option is only valid for one authentication method.
+ * MANDATORY_AUTH_ARG = check if a required option is set for an authentication method,
+ * reporting error if it's not.
+ */
+ #define INVALID_AUTH_OPTION(optname, validmethods) do {\
+ ereport(LOG, \
+ (errcode(ERRCODE_CONFIG_FILE_ERROR), \
+ errmsg("authentication option '%s' is only valid for authentication methods '%s'", \
+ optname, validmethods), \
+ errcontext("line %d of configuration file \"%s\"", \
+ line_num, HbaFileName))); \
+ goto hba_other_error; \
+ } while (0)
+
+ #define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) \
+ if (parsedline->auth_method != methodval) \
+ INVALID_AUTH_OPTION("ldaptls", "ldap")
+
+ #define MANDATORY_AUTH_ARG(argvar, argname, authname) \
+ if (argvar == NULL) {\
+ ereport(LOG, \
+ (errcode(ERRCODE_CONFIG_FILE_ERROR), \
+ errmsg("authentication method '%s' requires argument '%s' to be set", \
+ authname, argname), \
+ errcontext("line %d of configuration file \"%s\"", \
+ line_num, HbaFileName))); \
+ goto hba_other_error; \
+ } while (0);
+
+
+ /*
* Parse one line in the hba config file and store the result in
* a HbaLine structure.
*/
***************
*** 801,838 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
goto hba_other_error;
}

! /* Get the authentication argument token, if any */
! line_item = lnext(line_item);
! if (line_item)
{
token = lfirst(line_item);
- parsedline->auth_arg= pstrdup(token);
- }

! /*
! * Backwards compatible format of ident authentication - support "naked" ident map
! * name, as well as "sameuser"/"samerole"
! */
! if (parsedline->auth_method == uaIdent)
! {
! if (parsedline->auth_arg && strlen(parsedline->auth_arg))
{
! if (strcmp(parsedline->auth_arg, "sameuser\n") == 0 ||
! strcmp(parsedline->auth_arg, "samerole\n") == 0)
{
! /* This is now the default */
! pfree(parsedline->auth_arg);
! parsedline->auth_arg = NULL;
! parsedline->usermap = NULL;
}
else
{
! /* Specific ident map specified */
! parsedline->usermap = parsedline->auth_arg;
! parsedline->auth_arg = NULL;
}
}
}

return true;

--- 837,938 ----
goto hba_other_error;
}

! /* Parse remaining arguments */
! while ((line_item = lnext(line_item)) != NULL)
{
+ char *c;
+
token = lfirst(line_item);

! c = strchr(token, '=');
! if (c == NULL)
{
! /*
! * Got something that's not a name=value pair.
! *
! * XXX: attempt to do some backwards compatible parsing here?
! */
! ereport(LOG,
! (errcode(ERRCODE_CONFIG_FILE_ERROR),
! errmsg("authentication option not in name=value format: %s", token),
! errcontext("line %d of configuration file \"%s\"",
! line_num, HbaFileName)));
! goto hba_other_error;
! }
! else
! {
! *c++ = '\0'; /* token now holds "name", c holds "value" */
! if (strcmp(token, "map") == 0)
! {
! if (parsedline->auth_method != uaIdent &&
! parsedline->auth_method != uaKrb5 &&
! parsedline->auth_method != uaGSS &&
! parsedline->auth_method != uaSSPI)
! INVALID_AUTH_OPTION("map", "ident, krb5, gssapi and sspi");
! parsedline->usermap = pstrdup(c);
! }
! else if (strcmp(token, "pamservice") == 0)
! {
! REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
! parsedline->pamservice = pstrdup(c);
! }
! else if (strcmp(token, "ldaptls") == 0)
! {
! REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap");
! if (strcmp(c, "1") == 0)
! parsedline->ldaptls = true;
! else
! parsedline->ldaptls = false;
! }
! else if (strcmp(token, "ldapserver") == 0)
! {
! REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");
! parsedline->ldapserver = pstrdup(c);
! }
! else if (strcmp(token, "ldapport") == 0)
! {
! REQUIRE_AUTH_OPTION(uaLDAP, "ldapport", "ldap");
! parsedline->ldapport = atoi(c);
! if (parsedline->ldapport == 0)
! {
! ereport(LOG,
! (errcode(ERRCODE_CONFIG_FILE_ERROR),
! errmsg("invalid ldap port '%s'", c),
! errcontext("line %d of configuration file \"%s\"",
! line_num, HbaFileName)));
! goto hba_other_error;
! }
! }
! else if (strcmp(token, "ldapprefix") == 0)
{
! REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap");
! parsedline->ldapprefix = pstrdup(c);
! }
! else if (strcmp(token, "ldapsuffix") == 0)
! {
! REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap");
! parsedline->ldapsuffix = pstrdup(c);
}
else
{
! ereport(LOG,
! (errcode(ERRCODE_CONFIG_FILE_ERROR),
! errmsg("unknown authentication option name '%s'", token),
! errcontext("line %d of configuration file \"%s\"",
! line_num, HbaFileName)));
! goto hba_other_error;
}
}
}
+
+ /*
+ * Check if the selected authentication method has any mandatory arguments that
+ * are not set.
+ */
+ if (parsedline->auth_method == uaLDAP)
+ {
+ MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap");
+ }

return true;

***************
*** 1018,1025 **** free_hba_record(HbaLine *record)
pfree(record->database);
if (record->role)
pfree(record->role);
! if (record->auth_arg)
! pfree(record->auth_arg);
}

/*
--- 1118,1131 ----
pfree(record->database);
if (record->role)
pfree(record->role);
! if (record->pamservice)
! pfree(record->pamservice);
! if (record->ldapserver)
! pfree(record->ldapserver);
! if (record->ldapprefix)
! pfree(record->ldapprefix);
! if (record->ldapsuffix)
! pfree(record->ldapsuffix);
}

/*
***************
*** 1150,1156 **** read_pg_database_line(FILE *fp, char *dbname, Oid *dboid,
static void
parse_ident_usermap(List *line, int line_number, const char *usermap_name,
const char *pg_role, const char *ident_user,
! bool *found_p, bool *error_p)
{
ListCell *line_item;
char *token;
--- 1256,1262 ----
static void
parse_ident_usermap(List *line, int line_number, const char *usermap_name,
const char *pg_role, const char *ident_user,
! bool case_insensitive, bool *found_p, bool *error_p)
{
ListCell *line_item;
char *token;
***************
*** 1183,1192 **** parse_ident_usermap(List *line, int line_number, const char *usermap_name,
file_pgrole = token;

/* Match? */
! if (strcmp(file_map, usermap_name) == 0 &&
! strcmp(file_pgrole, pg_role) == 0 &&
! strcmp(file_ident_user, ident_user) == 0)
! *found_p = true;

return;

--- 1289,1308 ----
file_pgrole = token;

/* Match? */
! if (case_insensitive)
! {
! if (strcmp(file_map, usermap_name) == 0 &&
! pg_strcasecmp(file_pgrole, pg_role) == 0 &&
! pg_strcasecmp(file_ident_user, ident_user) == 0)
! *found_p = true;
! }
! else
! {
! if (strcmp(file_map, usermap_name) == 0 &&
! strcmp(file_pgrole, pg_role) == 0 &&
! strcmp(file_ident_user, ident_user) == 0)
! *found_p = true;
! }

return;

***************
*** 1210,1231 **** ident_syntax:
* file. That's an implied map where "pgrole" must be identical to
* "ident_user" in order to be authorized.
*
! * Iff authorized, return true.
*/
! bool
! check_ident_usermap(const char *usermap_name,
const char *pg_role,
! const char *ident_user)
{
bool found_entry = false,
error = false;

if (usermap_name == NULL || usermap_name[0] == '\0')
{
! if (strcmp(pg_role, ident_user) == 0)
! found_entry = true;
! else
! found_entry = false;
}
else
{
--- 1326,1357 ----
* file. That's an implied map where "pgrole" must be identical to
* "ident_user" in order to be authorized.
*
! * Iff authorized, return STATUS_OK, otherwise return STATUS_ERROR.
*/
! int
! check_usermap(const char *usermap_name,
const char *pg_role,
! const char *auth_user,
! bool case_insensitive)
{
bool found_entry = false,
error = false;

if (usermap_name == NULL || usermap_name[0] == '\0')
{
! if (case_insensitive)
! {
! if (pg_strcasecmp(pg_role, auth_user) == 0)
! return STATUS_OK;
! }
! else {
! if (strcmp(pg_role, auth_user) == 0)
! return STATUS_OK;
! }
! ereport(LOG,
! (errmsg("provided username (%s) and authenticated username (%s) don't match",
! auth_user, pg_role)));
! return STATUS_ERROR;
}
else
{
***************
*** 1235,1247 **** check_ident_usermap(const char *usermap_name,
forboth(line_cell, ident_lines, num_cell, ident_line_nums)
{
parse_ident_usermap(lfirst(line_cell), lfirst_int(num_cell),
! usermap_name, pg_role, ident_user,
&found_entry, &error);
if (found_entry || error)
break;
}
}
! return found_entry;
}


--- 1361,1380 ----
forboth(line_cell, ident_lines, num_cell, ident_line_nums)
{
parse_ident_usermap(lfirst(line_cell), lfirst_int(num_cell),
! usermap_name, pg_role, auth_user, case_insensitive,
&found_entry, &error);
if (found_entry || error)
break;
}
}
! if (!found_entry && !error)
! {
! ereport(LOG,
! (errmsg("no match in usermap for user '%s' authenticated as '%s'",
! pg_role, auth_user),
! errcontext("usermap '%s'", usermap_name)));
! }
! return found_entry?STATUS_OK:STATUS_ERROR;
}


*** a/src/backend/libpq/pg_hba.conf.sample
--- b/src/backend/libpq/pg_hba.conf.sample
***************
*** 9,18 ****
# are authenticated, which PostgreSQL user names they can use, which
# databases they can access. Records take one of these forms:
#
! # local DATABASE USER METHOD [OPTION]
! # host DATABASE USER CIDR-ADDRESS METHOD [OPTION]
! # hostssl DATABASE USER CIDR-ADDRESS METHOD [OPTION]
! # hostnossl DATABASE USER CIDR-ADDRESS METHOD [OPTION]
#
# (The uppercase items must be replaced by actual values.)
#
--- 9,18 ----
# are authenticated, which PostgreSQL user names they can use, which
# databases they can access. Records take one of these forms:
#
! # local DATABASE USER METHOD [OPTIONS]
! # host DATABASE USER CIDR-ADDRESS METHOD [OPTIONS]
! # hostssl DATABASE USER CIDR-ADDRESS METHOD [OPTIONS]
! # hostnossl DATABASE USER CIDR-ADDRESS METHOD [OPTIONS]
#
# (The uppercase items must be replaced by actual values.)
#
***************
*** 38,44 ****
# "krb5", "ident", "pam" or "ldap". Note that "password" sends passwords
# in clear text; "md5" is preferred since it sends encrypted passwords.
#
! # OPTION is the ident map or the name of the PAM service, depending on METHOD.
#
# Database and user names containing spaces, commas, quotes and other special
# characters must be quoted. Quoting one of the keywords "all", "sameuser" or
--- 38,47 ----
# "krb5", "ident", "pam" or "ldap". Note that "password" sends passwords
# in clear text; "md5" is preferred since it sends encrypted passwords.
#
! # OPTIONS are a set of options for the authentication in the format
! # NAME=VALUE. The available options depend on the different authentication
! # methods - refer to the "Client Authentication" section in the documentation
! # for a list of which options are available for which authentication methods.
#
# Database and user names containing spaces, commas, quotes and other special
# characters must be quoted. Quoting one of the keywords "all", "sameuser" or
*** a/src/backend/libpq/pg_ident.conf.sample
--- b/src/backend/libpq/pg_ident.conf.sample
***************
*** 5,22 ****
# Authentication" for a complete description. A short synopsis
# follows.
#
! # This file controls PostgreSQL ident-based authentication. It maps
! # ident user names (typically Unix user names) to their corresponding
# PostgreSQL user names. Records are of the form:
#
! # MAPNAME IDENT-USERNAME PG-USERNAME
#
# (The uppercase quantities must be replaced by actual values.)
#
# MAPNAME is the (otherwise freely chosen) map name that was used in
! # pg_hba.conf. IDENT-USERNAME is the detected user name of the
# client. PG-USERNAME is the requested PostgreSQL user name. The
! # existence of a record specifies that IDENT-USERNAME may connect as
# PG-USERNAME. Multiple maps may be specified in this file and used
# by pg_hba.conf.
#
--- 5,22 ----
# Authentication" for a complete description. A short synopsis
# follows.
#
! # This file controls PostgreSQL username mapping. It maps
! # external user names to their corresponding
# PostgreSQL user names. Records are of the form:
#
! # MAPNAME SYSTEM-USERNAME PG-USERNAME
#
# (The uppercase quantities must be replaced by actual values.)
#
# MAPNAME is the (otherwise freely chosen) map name that was used in
! # pg_hba.conf. SYSTEM-USERNAME is the detected user name of the
# client. PG-USERNAME is the requested PostgreSQL user name. The
! # existence of a record specifies that SYSTEM-USERNAME may connect as
# PG-USERNAME. Multiple maps may be specified in this file and used
# by pg_hba.conf.
#
***************
*** 28,35 ****
# Put your actual configuration here
# ----------------------------------
#
! # No map names are defined in the default configuration. If all ident
# user names and PostgreSQL user names are the same, you don't need
# this file.

! # MAPNAME IDENT-USERNAME PG-USERNAME
--- 28,35 ----
# Put your actual configuration here
# ----------------------------------
#
! # No map names are defined in the default configuration. If all system
# user names and PostgreSQL user names are the same, you don't need
# this file.

! # MAPNAME SYSTEM-USERNAME PG-USERNAME
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
***************
*** 25,37 **** typedef enum UserAuth
uaCrypt,
uaMD5,
uaGSS,
! uaSSPI
! #ifdef USE_PAM
! ,uaPAM
! #endif /* USE_PAM */
! #ifdef USE_LDAP
! ,uaLDAP
! #endif
} UserAuth;

typedef enum ConnType
--- 25,33 ----
uaCrypt,
uaMD5,
uaGSS,
! uaSSPI,
! uaPAM,
! uaLDAP
} UserAuth;

typedef enum ConnType
***************
*** 51,58 **** typedef struct
struct sockaddr_storage addr;
struct sockaddr_storage mask;
UserAuth auth_method;
char *usermap;
! char *auth_arg;
} HbaLine;

typedef struct Port hbaPort;
--- 47,60 ----
struct sockaddr_storage addr;
struct sockaddr_storage mask;
UserAuth auth_method;
+
char *usermap;
! char *pamservice;
! bool ldaptls;
! char *ldapserver;
! int ldapport;
! char *ldapprefix;
! char *ldapsuffix;
} HbaLine;

typedef struct Port hbaPort;
***************
*** 64,71 **** extern void load_role(void);
extern int hba_getauthmethod(hbaPort *port);
extern bool read_pg_database_line(FILE *fp, char *dbname, Oid *dboid,
Oid *dbtablespace, TransactionId *dbfrozenxid);
! extern bool check_ident_usermap(const char *usermap_name,
! const char *pg_role, const char *ident_user);
extern bool pg_isblank(const char c);

#endif /* HBA_H */
--- 66,74 ----
extern int hba_getauthmethod(hbaPort *port);
extern bool read_pg_database_line(FILE *fp, char *dbname, Oid *dboid,
Oid *dbtablespace, TransactionId *dbfrozenxid);
! extern int check_usermap(const char *usermap_name,
! const char *pg_role, const char *auth_user,
! bool case_sensitive);
extern bool pg_isblank(const char c);

#endif /* HBA_H */
This patch changes the options field of pg_hba.conf to take name/value
pairs instead of a fixed string. This makes it a lot nicer to deal with
auth methods that need more than one parameter, such as LDAP.

While at it, it also adds map support to kerberos, gssapi and sspi and
not just ident - basically all methods where the username comes from an
outside source (lmk if I missed one).

Also in passing, changes the methods in auth.c to deal with "unsupported
auth method on this platform" errors the same way for all authentication
methods.

I intend to build on this patch to support setting some
Kerberos/GSSAPI/SSPI parameters on a per-connection base, but wanted to
get the basics in first.

Obviously, documentation still pending. I'm working on that in parallel.


So, comments? Both in general, and specifically on if we need to do
backwards compatible parsing of LDAP options (doing it of all the other
options would be trivial, but LDAP would be harder)


//Magnus

No comments: