From 7356381ef57e6ddb09f1b1c72085e5102d0baa1e Mon Sep 17 00:00:00 2001 From: Magnus Hagander Date: Thu, 23 Oct 2008 13:31:10 +0000 Subject: [PATCH] * make pg_hba authoption be a set of 0 or more name=value pairs * make LDAP use this instead of the hacky previous method to specify the DN to bind as * make all auth options behave the same when they are not compiled into the server * rename "ident maps" to "user name maps", and support them for all auth methods that provide an external username This makes a backwards incompatible change in the format of pg_hba.conf for the ident, PAM and LDAP authentication methods. --- doc/src/sgml/client-auth.sgml | 359 +++++++++++++++++++++------------ src/backend/libpq/auth.c | 205 ++++--------------- src/backend/libpq/hba.c | 213 +++++++++++++++---- src/backend/libpq/pg_hba.conf.sample | 13 +- src/backend/libpq/pg_ident.conf.sample | 14 +- src/include/libpq/hba.h | 25 ++- 6 files changed, 476 insertions(+), 353 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 61f2b1e2e6..5a308eb895 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1,4 +1,4 @@ - + Client Authentication @@ -96,13 +96,13 @@ A record can have one of the seven formats -local database user auth-method auth-option -host database user CIDR-address auth-method auth-option -hostssl database user CIDR-address auth-method auth-option -hostnossl database user CIDR-address auth-method auth-option -host database user IP-address IP-mask auth-method auth-option -hostssl database user IP-address IP-mask auth-method auth-option -hostnossl database user IP-address IP-mask auth-method auth-option +local database user auth-method auth-options +host database user CIDR-address auth-method auth-options +hostssl database user CIDR-address auth-method auth-options +hostnossl database user CIDR-address auth-method auth-options +host database user IP-address IP-mask auth-method auth-options +hostssl database user IP-address IP-mask auth-method auth-options +hostnossl database user IP-address IP-mask auth-method auth-options The meaning of the fields is as follows: @@ -422,11 +422,13 @@ hostnossl database user - auth-option + auth-options - The meaning of this optional field depends on the chosen - authentication method. Details appear below. + This field contains zero or more name-value pairs with + extra options passed to this authentication method. Details + about which options are available for which authentication + method appear below. @@ -534,7 +536,7 @@ host all all 0.0.0.0/0 krb5 # "omicron" that says "bryanh" is allowed to connect as "guest1". # # TYPE DATABASE USER CIDR-ADDRESS METHOD -host all all 192.168.0.0/16 ident omicron +host all all 192.168.0.0/16 ident map=omicron # If these are the only three lines for local connections, they will # allow local users to connect only to their own databases (databases @@ -557,6 +559,92 @@ local db1,db2,@demodbs all md5 + + Username maps + + + Username maps + + + + When using an external authentication system like Ident or GSSAPI, + the name of the operating system user that initiated the connection may + not be the same as the database user he is requesting to connect as. + In this case, a user name map can be applied to map the operating system + username to a database user, using the pg_ident.conf + file. In order to use username mapping, specify + map=map-name + in the options field in pg_hba.conf. This option is + supported for all authentication methods that receive external usernames. + Since the pg_ident.conf file can contain multiple maps, + the name of the map to be used is specified in the + map-name parameter in pg_hba.conf + to indicate which map to use for each individual connection. + + + + Ident maps are defined in the ident map file, which by default is named + pg_ident.confpg_ident.conf + and is stored in the + cluster's data directory. (It is possible to place the map file + elsewhere, however; see the + configuration parameter.) + The ident map file contains lines of the general form: + +map-name system-username database-username + + Comments and whitespace are handled in the same way as in + pg_hba.conf. The + map-name is an arbitrary name that will be used to + refer to this mapping in pg_hba.conf. The other + two fields specify which operating system user is allowed to connect + as which database user. The same map-name can be + used repeatedly to specify more user-mappings within a single map. + There is no restriction regarding how many database users a given + operating system user can correspond to, nor vice versa. + + + + The pg_ident.conf file is read on start-up and + when the main server process receives a + SIGHUPSIGHUP + signal. If you edit the file on an + active system, you will need to signal the server + (using pg_ctl reload or kill -HUP) to make it + re-read the file. + + + + A pg_ident.conf file that could be used in + conjunction with the pg_hba.conf file in is shown in . In this example setup, anyone + logged in to a machine on the 192.168 network that does not have the + Unix user name bryanh, ann, or + robert would not be granted access. Unix user + robert would only be allowed access when he tries to + connect as PostgreSQL user bob, not + as robert or anyone else. ann would + only be allowed to connect as ann. User + bryanh would be allowed to connect as either + bryanh himself or as guest1. + + + + An example <filename>pg_ident.conf</> file + +# MAPNAME IDENT-USERNAME PG-USERNAME + +omicron bryanh bryanh +omicron ann ann +# bob has user name robert on these machines +omicron robert bob +# bryanh can also connect as guest1 +omicron bryanh guest1 + + + + Authentication methods @@ -685,7 +773,21 @@ local db1,db2,@demodbs all md5 GSSAPI support has to be enabled when PostgreSQL is built; see for more information. - + + + The following configuration options are supported for GSSAPI: + + + map + + + Allows for mapping between system and database usernames. See + for details. + + + + + @@ -713,6 +815,20 @@ local db1,db2,@demodbs all md5 for details. + + The following configuration options are supported for SSPI: + + + map + + + Allows for mapping between system and database usernames. See + for details. + + + + + @@ -846,6 +962,21 @@ local db1,db2,@demodbs all md5 depending on the connection type. + + The following configuration options are supported for GSSAPI: + + + map + + + Allows for mapping between system and database usernames. See + for details. + + + + + + Ident Authentication over TCP/IP @@ -918,83 +1049,6 @@ local db1,db2,@demodbs all md5 - - Ident Maps - - - When using ident-based authentication, after having determined the - name of the operating system user that initiated the connection, - PostgreSQL checks whether that user is - allowed to connect as the database user he is requesting to connect - as. This is controlled by the ident map argument that follows the - ident key word in the pg_hba.conf - file. If an ident map is not specified, the database user will be - checked with the same name as the operating system user. Other maps - must be created manually. - - - - Ident maps are defined in the ident map file, which by default is named - pg_ident.confpg_ident.conf - and is stored in the - cluster's data directory. (It is possible to place the map file - elsewhere, however; see the - configuration parameter.) - The ident map file contains lines of the general form: - -map-name ident-username database-username - - Comments and whitespace are handled in the same way as in - pg_hba.conf. The - map-name is an arbitrary name that will be used to - refer to this mapping in pg_hba.conf. The other - two fields specify which operating system user is allowed to connect - as which database user. The same map-name can be - used repeatedly to specify more user-mappings within a single map. - There is no restriction regarding how many database users a given - operating system user can correspond to, nor vice versa. - - - - The pg_ident.conf file is read on start-up and - when the main server process receives a - SIGHUPSIGHUP - signal. If you edit the file on an - active system, you will need to signal the server - (using pg_ctl reload or kill -HUP) to make it - re-read the file. - - - - A pg_ident.conf file that could be used in - conjunction with the pg_hba.conf file in is shown in . In this example setup, anyone - logged in to a machine on the 192.168 network that does not have the - Unix user name bryanh, ann, or - robert would not be granted access. Unix user - robert would only be allowed access when he tries to - connect as PostgreSQL user bob, not - as robert or anyone else. ann would - only be allowed to connect as ann. User - bryanh would be allowed to connect as either - bryanh himself or as guest1. - - - - An example <filename>pg_ident.conf</> file - -# MAPNAME IDENT-USERNAME PG-USERNAME - -omicron bryanh bryanh -omicron ann ann -# bob has user name robert on these machines -omicron robert bob -# bryanh can also connect as guest1 -omicron bryanh guest1 - - - @@ -1007,49 +1061,84 @@ omicron bryanh guest1 This authentication method operates similarly to password except that it uses LDAP - as the authentication method. LDAP is used only to validate + as the password verification method. LDAP is used only to validate the user name/password pairs. Therefore the user must already exist in the database before LDAP can be used for - authentication. The server and parameters used are specified - after the ldap key word in the file - pg_hba.conf. The format of this parameter is: - -ldap[s]://servername[:port]/base dn[;prefix[;suffix]] - - Commas are used to specify multiple items in an ldap - component. However, because unquoted commas are treated as item - separators in pg_hba.conf, it is wise to - double-quote the ldap URL to preserve any commas present, - e.g.: - -"ldap://ldap.example.net/dc=example,dc=net;EXAMPLE\" - - - - - If ldaps is specified instead of ldap, - TLS encryption will be enabled for the connection. Note that this - will encrypt only the connection between the PostgreSQL server - and the LDAP server. The connection between the client and the - PostgreSQL server is not affected by this setting. To make use of - TLS encryption, you might need to configure the LDAP library prior - to configuring PostgreSQL. Note that encrypted LDAP is available only - if the platform's LDAP library supports it. - - - If no port is specified, the default port as configured in the - LDAP library will be used. + authentication. + - The server will bind to the distinguished name specified as - base dn using the user name supplied by the client. - If prefix and suffix is - specified, it will be prepended and appended to the user name + The server will bind to the distinguished name constructed as + prefix username suffix. before the bind. Typically, the prefix parameter is used to specify cn=, or DOMAIN\ in an Active - Directory environment. + Directory environment, and suffix is used to specify the remaining part + of the DN in a non-Active Directory environment. - + + + The following configuration options are supported for LDAP: + + + ldapserver + + + Name or IP of LDAP server to connect to. + + + + + ldapprefix + + + String to prepend to the username when building the base DN to + bind as. + + + + + ldapsuffix + + + String to append to the username when building the base DN to + bind as. + + + + + ldapport + + + Port number on LDAP server to connect to. If no port is specified, + the default port in the LDAP library will be used. + + + + + ldaptls + + + Set to 1 to make the connection between PostgreSQL and the + LDAP server use TLS encryption. Note that this only encrypts + the traffic to the LDAP server - the connection to the client + may still be unencrypted unless TLS is used there as well. + + + + + + + + + Since LDAP often uses commas and spaces to separate the different + parts of a DN, it is advised to always use double-quoted parameter + values when configuring LDAP options, such as: + + + +ldapserver=ldap.example.net prefix="cn=" suffix="dc=example, dc=net" + + @@ -1063,9 +1152,7 @@ ldap[s]://servername[:port]/password except that it uses PAM (Pluggable Authentication Modules) as the authentication mechanism. The - default PAM service name is postgresql. You can - optionally supply your own service name after the pam - key word in the file pg_hba.conf. + default PAM service name is postgresql. PAM is used only to validate user name/password pairs. Therefore the user must already exist in the database before PAM can be used for authentication. For more information about @@ -1075,6 +1162,20 @@ ldap[s]://servername[:port]/Solaris PAM Page. + + The following configuration options are supported for PAM: + + + pamservice + + + PAM service name. + + + + + + If PAM is set up to read /etc/shadow, authentication diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index f01be3b18f..865d52fc56 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.168 2008/09/15 12:32:56 mha Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.169 2008/10/23 13:31:10 mha Exp $ * *------------------------------------------------------------------------- */ @@ -126,9 +126,8 @@ char *pg_krb_realm = NULL; * MIT Kerberos authentication system - protocol version 5 *---------------------------------------------------------------- */ -static int pg_krb5_recvauth(Port *port); - #ifdef KRB5 +static int pg_krb5_recvauth(Port *port); #include /* Some old versions of Kerberos do not include in */ @@ -150,14 +149,14 @@ static krb5_principal pg_krb5_server; * GSSAPI Authentication *---------------------------------------------------------------- */ -static int pg_GSS_recvauth(Port *port); - #ifdef ENABLE_GSS #if defined(HAVE_GSSAPI_H) #include #else #include #endif + +static int pg_GSS_recvauth(Port *port); #endif /* ENABLE_GSS */ @@ -165,12 +164,11 @@ 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 **); +static int pg_SSPI_recvauth(Port *port); #endif @@ -236,16 +234,12 @@ 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; @@ -316,18 +310,30 @@ ClientAuthentication(Port *port) } 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,18 +383,22 @@ ClientAuthentication(Port *port) status = recv_and_check_password_packet(port); break; -#ifdef USE_PAM case uaPAM: +#ifdef USE_PAM pam_port_cludge = port; status = CheckPAMAuth(port, port->user_name, ""); - break; +#else + Assert(false); #endif /* USE_PAM */ + break; -#ifdef USE_LDAP case uaLDAP: +#ifdef USE_LDAP status = CheckLDAPAuth(port); - break; +#else + Assert(false); #endif + break; case uaTrust: status = STATUS_OK; @@ -713,19 +723,8 @@ 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; + 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,16 +732,6 @@ 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 */ @@ -1020,38 +1009,13 @@ 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; - } + ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, + pg_krb_caseins_users); 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 */ @@ -1328,30 +1292,8 @@ 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; + return check_usermap(port->hba->usermap, port->user_name, accountname, true); } - -#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 */ @@ -1795,14 +1737,7 @@ 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; + return check_usermap(port->hba->usermap, port->user_name, ident_user, false); } @@ -1913,8 +1848,8 @@ 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@", + 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,76 +1935,20 @@ 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') + if (!port->hba->ldapserver|| port->hba->ldapserver[0] == '\0') { ereport(LOG, - (errmsg("LDAP configuration URL not specified"))); + (errmsg("LDAP server not specified"))); return STATUS_ERROR; } - /* - * Crack the LDAP url. We do a very trivial parse: - * - * ldap[s]://[:]/[;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; - } + if (port->hba->ldapport == 0) + port->hba->ldapport = LDAP_PORT; sendAuthRequest(port, AUTH_REQ_PASSWORD); @@ -2077,7 +1956,7 @@ CheckLDAPAuth(Port *port) if (passwd == NULL) return STATUS_EOF; /* client wouldn't send password */ - ldap = ldap_init(server, ldapport); + ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport); if (!ldap) { #ifndef WIN32 @@ -2100,7 +1979,7 @@ CheckLDAPAuth(Port *port) return STATUS_ERROR; } - if (ssl) + if (port->hba->ldaptls) { #ifndef WIN32 if ((r = ldap_start_tls_s(ldap, NULL, NULL)) != LDAP_SUCCESS) @@ -2155,7 +2034,9 @@ CheckLDAPAuth(Port *port) } snprintf(fulluser, sizeof(fulluser), "%s%s%s", - prefix, port->user_name, suffix); + 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,7 +2046,7 @@ CheckLDAPAuth(Port *port) { ereport(LOG, (errmsg("LDAP login failed for user \"%s\" on server \"%s\": error code %d", - fulluser, server, r))); + fulluser, port->hba->ldapserver, r))); return STATUS_ERROR; } diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 6917e5c927..ddb7fc5696 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.168 2008/09/15 20:55:04 mha Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.169 2008/10/23 13:31:10 mha Exp $ * *------------------------------------------------------------------------- */ @@ -565,6 +565,44 @@ check_db(const char *dbname, const char *role, char *param_str) /* + * 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) do {\ + if (parsedline->auth_method != methodval) \ + INVALID_AUTH_OPTION("ldaptls", "ldap"); \ +} while (0); + +#define MANDATORY_AUTH_ARG(argvar, argname, authname) do {\ + 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,38 +839,102 @@ 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) + /* Parse remaining arguments */ + while ((line_item = lnext(line_item)) != NULL) { + char *c; + 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)) + c = strchr(token, '='); + if (c == NULL) { - if (strcmp(parsedline->auth_arg, "sameuser\n") == 0 || - strcmp(parsedline->auth_arg, "samerole\n") == 0) + /* + * 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) { - /* This is now the default */ - pfree(parsedline->auth_arg); - parsedline->auth_arg = NULL; - parsedline->usermap = NULL; + 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 { - /* Specific ident map specified */ - parsedline->usermap = parsedline->auth_arg; - parsedline->auth_arg = NULL; + 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,8 +1120,14 @@ free_hba_record(HbaLine *record) pfree(record->database); if (record->role) pfree(record->role); - if (record->auth_arg) - pfree(record->auth_arg); + 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,7 +1258,7 @@ 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) + bool case_insensitive, bool *found_p, bool *error_p) { ListCell *line_item; char *token; @@ -1183,10 +1291,20 @@ 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; + 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,22 +1328,32 @@ 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. + * Iff authorized, return STATUS_OK, otherwise return STATUS_ERROR. */ -bool -check_ident_usermap(const char *usermap_name, +int +check_usermap(const char *usermap_name, const char *pg_role, - const char *ident_user) + const char *auth_user, + bool case_insensitive) { 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; + 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,13 +1363,20 @@ 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, + usermap_name, pg_role, auth_user, case_insensitive, &found_entry, &error); if (found_entry || error) break; } } - return found_entry; + 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; } diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample index 1447a8c4be..c84d955e1b 100644 --- a/src/backend/libpq/pg_hba.conf.sample +++ b/src/backend/libpq/pg_hba.conf.sample @@ -9,10 +9,10 @@ # 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] +# 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,7 +38,10 @@ # "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. +# 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 diff --git a/src/backend/libpq/pg_ident.conf.sample b/src/backend/libpq/pg_ident.conf.sample index 6f0de64910..4471d56e13 100644 --- a/src/backend/libpq/pg_ident.conf.sample +++ b/src/backend/libpq/pg_ident.conf.sample @@ -5,18 +5,18 @@ # 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 +# This file controls PostgreSQL username mapping. It maps +# external user names to their corresponding # PostgreSQL user names. Records are of the form: # -# MAPNAME IDENT-USERNAME PG-USERNAME +# 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. IDENT-USERNAME is the detected user name of the +# 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 IDENT-USERNAME may connect as +# 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,8 +28,8 @@ # Put your actual configuration here # ---------------------------------- # -# No map names are defined in the default configuration. If all ident +# 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 IDENT-USERNAME PG-USERNAME +# MAPNAME SYSTEM-USERNAME PG-USERNAME diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 3af7db9e62..54ecc560d8 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -4,7 +4,7 @@ * Interface to hba.c * * - * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.49 2008/09/15 12:32:57 mha Exp $ + * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.50 2008/10/23 13:31:10 mha Exp $ * *------------------------------------------------------------------------- */ @@ -25,13 +25,9 @@ typedef enum UserAuth uaCrypt, uaMD5, uaGSS, - uaSSPI -#ifdef USE_PAM - ,uaPAM -#endif /* USE_PAM */ -#ifdef USE_LDAP - ,uaLDAP -#endif + uaSSPI, + uaPAM, + uaLDAP } UserAuth; typedef enum ConnType @@ -51,8 +47,14 @@ typedef struct struct sockaddr_storage addr; struct sockaddr_storage mask; UserAuth auth_method; + char *usermap; - char *auth_arg; + char *pamservice; + bool ldaptls; + char *ldapserver; + int ldapport; + char *ldapprefix; + char *ldapsuffix; } HbaLine; typedef struct Port hbaPort; @@ -64,8 +66,9 @@ 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 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 */ -- 2.11.0