Обсуждение: pg_hba options parsing

Поиск
Список
Период
Сортировка

pg_hba options parsing

От
Magnus Hagander
Дата:
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
*** 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 */

Re: pg_hba options parsing

От
Magnus Hagander
Дата:
Magnus Hagander wrote:
> 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)

Updated version of this patch, now with doc changes.

//Magnus

*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
***************
*** 96,108 ****
    <para>
     A record can have one of the seven formats
  <synopsis>
! local      <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>auth-method</replaceable> <optional><replaceable>auth-option</replaceable></optional> 
! host       <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>CIDR-address</replaceable> <replaceable>auth-method</replaceable>
<optional><replaceable>auth-option</replaceable></optional>
! hostssl    <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>CIDR-address</replaceable> <replaceable>auth-method</replaceable>
<optional><replaceable>auth-option</replaceable></optional>
! hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>CIDR-address</replaceable> <replaceable>auth-method</replaceable>
<optional><replaceable>auth-option</replaceable></optional>
! host       <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable>  <replaceable>auth-method</replaceable>
<optional><replaceable>auth-option</replaceable></optional>
! hostssl    <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable>  <replaceable>auth-method</replaceable>
<optional><replaceable>auth-option</replaceable></optional>
! hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable>  <replaceable>auth-method</replaceable>
<optional><replaceable>auth-option</replaceable></optional>
  </synopsis>
     The meaning of the fields is as follows:

--- 96,108 ----
    <para>
     A record can have one of the seven formats
  <synopsis>
! local      <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> 
! host       <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>CIDR-address</replaceable> <replaceable>auth-method</replaceable>
<optional><replaceable>auth-options</replaceable></optional>
! hostssl    <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>CIDR-address</replaceable> <replaceable>auth-method</replaceable>
<optional><replaceable>auth-options</replaceable></optional>
! hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>CIDR-address</replaceable> <replaceable>auth-method</replaceable>
<optional><replaceable>auth-options</replaceable></optional>
! host       <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable>  <replaceable>auth-method</replaceable>
<optional><replaceable>auth-options</replaceable></optional>
! hostssl    <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable>  <replaceable>auth-method</replaceable>
<optional><replaceable>auth-options</replaceable></optional>
! hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
<replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable>  <replaceable>auth-method</replaceable>
<optional><replaceable>auth-options</replaceable></optional>
  </synopsis>
     The meaning of the fields is as follows:

***************
*** 422,432 **** hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
      </varlistentry>

      <varlistentry>
!      <term><replaceable>auth-option</replaceable></term>
       <listitem>
        <para>
!        The meaning of this optional field depends on the chosen
!        authentication method.  Details appear below.
        </para>
       </listitem>
      </varlistentry>
--- 422,434 ----
      </varlistentry>

      <varlistentry>
!      <term><replaceable>auth-options</replaceable></term>
       <listitem>
        <para>
!        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.
        </para>
       </listitem>
      </varlistentry>
***************
*** 534,540 **** 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

  # If these are the only three lines for local connections, they will
  # allow local users to connect only to their own databases (databases
--- 536,542 ----
  # "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 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,562 **** local   db1,db2,@demodbs  all                         md5
--- 559,650 ----
     </example>
   </sect1>

+  <sect1 id="auth-username-maps">
+   <title>Username maps</title>
+
+   <indexterm zone="auth-username-maps">
+    <primary>Username maps</primary>
+   </indexterm>
+
+   <para>
+    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 <filename>pg_ident.conf</filename>
+    file. In order to use username mapping, specify
+    <literal>map</literal>=<replaceable>map-name</replaceable>
+    in the options field in <filename>pg_hba.conf</filename>. This option is
+    supported for all authentication methods that receive external usernames.
+    Since the <filename>pg_ident.conf</filename> file can contain multiple maps,
+    the name of the map to be used is specified in the
+    <replaceable>map-name</replaceable> parameter in <filename>pg_hba.conf</filename>
+    to indicate which map to use for each individual connection.
+   </para>
+
+   <para>
+    Ident maps are defined in the ident map file, which by default is named
+    <filename>pg_ident.conf</><indexterm><primary>pg_ident.conf</primary></indexterm>
+    and is stored in the
+    cluster's data directory.  (It is possible to place the map file
+    elsewhere, however; see the <xref linkend="guc-ident-file">
+    configuration parameter.)
+    The ident map file contains lines of the general form:
+ <synopsis>
+ <replaceable>map-name</> <replaceable>system-username</> <replaceable>database-username</>
+ </synopsis>
+    Comments and whitespace are handled in the same way as in
+    <filename>pg_hba.conf</>.  The
+    <replaceable>map-name</> is an arbitrary name that will be used to
+    refer to this mapping in <filename>pg_hba.conf</filename>. The other
+    two fields specify which operating system user is allowed to connect
+    as which database user. The same <replaceable>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.
+   </para>
+
+   <para>
+    The <filename>pg_ident.conf</filename> file is read on start-up and
+    when the main server process receives a
+    <systemitem>SIGHUP</systemitem><indexterm><primary>SIGHUP</primary></indexterm>
+    signal. If you edit the file on an
+    active system, you will need to signal the server
+    (using <literal>pg_ctl reload</> or <literal>kill -HUP</>) to make it
+    re-read the file.
+   </para>
+
+   <para>
+    A <filename>pg_ident.conf</filename> file that could be used in
+    conjunction with the <filename>pg_hba.conf</> file in <xref
+    linkend="example-pg-hba.conf"> is shown in <xref
+    linkend="example-pg-ident.conf">. In this example setup, anyone
+    logged in to a machine on the 192.168 network that does not have the
+    Unix user name <literal>bryanh</>, <literal>ann</>, or
+    <literal>robert</> would not be granted access. Unix user
+    <literal>robert</> would only be allowed access when he tries to
+    connect as <productname>PostgreSQL</> user <literal>bob</>, not
+    as <literal>robert</> or anyone else. <literal>ann</> would
+    only be allowed to connect as <literal>ann</>. User
+    <literal>bryanh</> would be allowed to connect as either
+    <literal>bryanh</> himself or as <literal>guest1</>.
+   </para>
+
+   <example id="example-pg-ident.conf">
+    <title>An example <filename>pg_ident.conf</> file</title>
+ <programlisting>
+ # 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
+ </programlisting>
+   </example>
+  </sect1>
+
   <sect1 id="auth-methods">
    <title>Authentication methods</title>
    <para>
***************
*** 685,691 **** local   db1,db2,@demodbs  all                         md5
      GSSAPI support has to be enabled when <productname>PostgreSQL</> is built;
      see <xref linkend="installation"> for more information.
     </para>
!
    </sect2>

    <sect2 id="sspi-auth">
--- 773,793 ----
      GSSAPI support has to be enabled when <productname>PostgreSQL</> is built;
      see <xref linkend="installation"> for more information.
     </para>
!
!    <para>
!     The following configuration options are supported for <productname>GSSAPI</productname>:
!     <variablelist>
!      <varlistentry>
!       <term>map</term>
!       <listitem>
!        <para>
!         Allows for mapping between system and database usernames. See
!         <xref linkend="auth-username-maps"> for details.
!        </para>
!       </listitem>
!      </varlistentry>
!     </variablelist>
!    </para>
    </sect2>

    <sect2 id="sspi-auth">
***************
*** 713,718 **** local   db1,db2,@demodbs  all                         md5
--- 815,834 ----
      for details.
     </para>

+    <para>
+     The following configuration options are supported for <productname>SSPI</productname>:
+     <variablelist>
+      <varlistentry>
+       <term>map</term>
+       <listitem>
+        <para>
+         Allows for mapping between system and database usernames. See
+         <xref linkend="auth-username-maps"> for details.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </para>
    </sect2>

    <sect2 id="kerberos-auth">
***************
*** 846,851 **** local   db1,db2,@demodbs  all                         md5
--- 962,982 ----
      depending on the connection type.
     </para>

+    <para>
+     The following configuration options are supported for <productname>GSSAPI</productname>:
+     <variablelist>
+      <varlistentry>
+       <term>map</term>
+       <listitem>
+        <para>
+         Allows for mapping between system and database usernames. See
+         <xref linkend="auth-username-maps"> for details.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </para>
+
     <sect3>
      <title>Ident Authentication over TCP/IP</title>

***************
*** 918,1000 **** local   db1,db2,@demodbs  all                         md5
      </para>
      </sect3>

-    <sect3 id="auth-ident-maps">
-     <title>Ident Maps</title>
-
-    <para>
-     When using ident-based authentication, after having determined the
-     name of the operating system user that initiated the connection,
-     <productname>PostgreSQL</productname> 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
-     <literal>ident</> key word in the <filename>pg_hba.conf</filename>
-     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.
-    </para>
-
-    <para>
-     Ident maps are defined in the ident map file, which by default is named
-     <filename>pg_ident.conf</><indexterm><primary>pg_ident.conf</primary></indexterm>
-     and is stored in the
-     cluster's data directory.  (It is possible to place the map file
-     elsewhere, however; see the <xref linkend="guc-ident-file">
-     configuration parameter.)
-     The ident map file contains lines of the general form:
- <synopsis>
- <replaceable>map-name</> <replaceable>ident-username</> <replaceable>database-username</>
- </synopsis>
-     Comments and whitespace are handled in the same way as in
-     <filename>pg_hba.conf</>.  The
-     <replaceable>map-name</> is an arbitrary name that will be used to
-     refer to this mapping in <filename>pg_hba.conf</filename>. The other
-     two fields specify which operating system user is allowed to connect
-     as which database user. The same <replaceable>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.
-    </para>
-
-   <para>
-    The <filename>pg_ident.conf</filename> file is read on start-up and
-    when the main server process receives a
-    <systemitem>SIGHUP</systemitem><indexterm><primary>SIGHUP</primary></indexterm>
-    signal. If you edit the file on an
-    active system, you will need to signal the server
-    (using <literal>pg_ctl reload</> or <literal>kill -HUP</>) to make it
-    re-read the file.
-   </para>
-
-    <para>
-     A <filename>pg_ident.conf</filename> file that could be used in
-     conjunction with the <filename>pg_hba.conf</> file in <xref
-     linkend="example-pg-hba.conf"> is shown in <xref
-     linkend="example-pg-ident.conf">. In this example setup, anyone
-     logged in to a machine on the 192.168 network that does not have the
-     Unix user name <literal>bryanh</>, <literal>ann</>, or
-     <literal>robert</> would not be granted access. Unix user
-     <literal>robert</> would only be allowed access when he tries to
-     connect as <productname>PostgreSQL</> user <literal>bob</>, not
-     as <literal>robert</> or anyone else. <literal>ann</> would
-     only be allowed to connect as <literal>ann</>. User
-     <literal>bryanh</> would be allowed to connect as either
-     <literal>bryanh</> himself or as <literal>guest1</>.
-    </para>
-
-    <example id="example-pg-ident.conf">
-     <title>An example <filename>pg_ident.conf</> file</title>
- <programlisting>
- # 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
- </programlisting>
-    </example>
-    </sect3>
    </sect2>

    <sect2 id="auth-ldap">
--- 1049,1054 ----
***************
*** 1007,1055 **** omicron       bryanh            guest1
     <para>
      This authentication method operates similarly to
      <literal>password</literal> except that it uses LDAP
!     as the authentication 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 <literal>ldap</> key word in the file
!     <filename>pg_hba.conf</filename>. The format of this parameter is:
!     <synopsis>
! ldap[<replaceable>s</>]://<replaceable>servername</>[:<replaceable>port</>]/<replaceable>base
dn</replaceable>[;<replaceable>prefix</>[;<replaceable>suffix</>]]
!     </synopsis>
!     Commas are used to specify multiple items in an <literal>ldap</>
!     component.  However, because unquoted commas are treated as item
!     separators in <filename>pg_hba.conf</filename>, it is wise to
!     double-quote the <literal>ldap</> URL to preserve any commas present,
!     e.g.:
!     <synopsis>
! "ldap://ldap.example.net/dc=example,dc=net;EXAMPLE\"
!     </synopsis>
!
!    </para>
!    <para>
!     If <literal>ldaps</> is specified instead of <literal>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.
!    </para>
!    <para>
!     If no port is specified, the default port as configured in the
!     LDAP library will be used.
     </para>
     <para>
!     The server will bind to the distinguished name specified as
!     <replaceable>base dn</> using the user name supplied by the client.
!     If <replaceable>prefix</> and <replaceable>suffix</> is
!     specified, it will be prepended and appended to the user name
      before the bind. Typically, the prefix parameter is used to specify
      <replaceable>cn=</>, or <replaceable>DOMAIN\</> in an Active
!     Directory environment.
     </para>
!
    </sect2>

    <sect2 id="auth-pam">
--- 1061,1144 ----
     <para>
      This authentication method operates similarly to
      <literal>password</literal> except that it uses LDAP
!     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.
     </para>
+
     <para>
!     The server will bind to the distinguished name constructed as
!     <replaceable>prefix</> <replaceable>username</> <replaceable>suffix</>.
      before the bind. Typically, the prefix parameter is used to specify
      <replaceable>cn=</>, or <replaceable>DOMAIN\</> in an Active
!     Directory environment, and suffix is used to specify the remaining part
!     of the DN in a non-Active Directory environment.
     </para>
!
!    <para>
!     The following configuration options are supported for LDAP:
!     <variablelist>
!      <varlistentry>
!       <term>ldapserver</term>
!       <listitem>
!        <para>
!         Name or IP of LDAP server to connect to.
!        </para>
!       </listitem>
!      </varlistentry>
!      <varlistentry>
!       <term>ldapprefix</term>
!       <listitem>
!        <para>
!         String to prepend to the username when building the base DN to
!         bind as.
!        </para>
!       </listitem>
!      </varlistentry>
!      <varlistentry>
!       <term>ldapsuffix</term>
!       <listitem>
!        <para>
!         String to append to the username when building the base DN to
!         bind as.
!        </para>
!       </listitem>
!      </varlistentry>
!      <varlistentry>
!       <term>ldapport</term>
!       <listitem>
!        <para>
!         Port number on LDAP server to connect to. If no port is specified,
!         the default port in the LDAP library will be used.
!        </para>
!       </listitem>
!      </varlistentry>
!      <varlistentry>
!       <term>ldaptls</term>
!       <listitem>
!        <para>
!         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.
!        </para>
!       </listitem>
!      </varlistentry>
!     </variablelist>
!    </para>
!
!    <note>
!     <para>
!      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:
!     </para>
!    </note>
!     <synopsis>
! ldapserver=ldap.example.net prefix="cn=" suffix="dc=example, dc=net"
!     </synopsis>
!
    </sect2>

    <sect2 id="auth-pam">
***************
*** 1063,1071 **** ldap[<replaceable>s</>]://<replaceable>servername</>[:<replaceable>port</>]/<rep
      This authentication method operates similarly to
      <literal>password</literal> except that it uses PAM (Pluggable
      Authentication Modules) as the authentication mechanism. The
!     default PAM service name is <literal>postgresql</literal>. You can
!     optionally supply your own service name after the <literal>pam</>
!     key word in the file <filename>pg_hba.conf</filename>.
      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
--- 1152,1158 ----
      This authentication method operates similarly to
      <literal>password</literal> except that it uses PAM (Pluggable
      Authentication Modules) as the authentication mechanism. The
!     default PAM service name is <literal>postgresql</literal>.
      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,1080 **** ldap[<replaceable>s</>]://<replaceable>servername</>[:<replaceable>port</>]/<rep
--- 1162,1181 ----
      <systemitem class="osname">Solaris</> PAM Page</ulink>.
     </para>

+    <para>
+     The following configuration options are supported for PAM:
+     <variablelist>
+      <varlistentry>
+       <term>pamservice</term>
+       <listitem>
+        <para>
+         PAM service name.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </para>
+
     <note>
      <para>
       If PAM is set up to read <filename>/etc/shadow</>, authentication
*** 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,394 **** 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:
              status = STATUS_OK;
--- 383,404 ----
              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);
  #endif   /* USE_PAM */
+             break;

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

          case uaTrust:
              status = STATUS_OK;
***************
*** 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 */

Re: pg_hba options parsing

От
Magnus Hagander
Дата:
Bruce Momjian wrote:
> OK, a few comments:
> 
> This should have spaces:
> 
>     !            port->hba->ldapprefix?port->hba->ldapprefix:"",

Fixed. Though I guess pgindent would've fixed it eventually otherwise.


> This is missing 'do' or something:
> 
>     + #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);

Wow.Amazing that it actually compiles and work. I guess it treats the
while(0) as a separate statement completely.

The correct fix is, AFAICS, to remove the while(0).


> Seems we are losing the sscanf(), which is good.

Yes, that is one of the goals :-)


If there are no further comments I will go ahead and commit this (with
the above fixes) within a couple of days.

//Magnus



Re: pg_hba options parsing

От
Tom Lane
Дата:
Magnus Hagander <magnus@hagander.net> writes:
> Bruce Momjian wrote:
>> This is missing 'do' or something:
>> 
>> + #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);

> Wow.Amazing that it actually compiles and work. I guess it treats the
> while(0) as a separate statement completely.

> The correct fix is, AFAICS, to remove the while(0).

Absolutely not!  The reason for using do/while in this sort of situation
is to make sure that the "if" can't get matched up to an "else" in code
following the macro.  Without do/while this macro will be a loaded
foot-gun.
        regards, tom lane


Re: pg_hba options parsing

От
Magnus Hagander
Дата:
Tom Lane wrote:
> Magnus Hagander <magnus@hagander.net> writes:
>> Bruce Momjian wrote:
>>> This is missing 'do' or something:
>>>
>>> + #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);
> 
>> Wow.Amazing that it actually compiles and work. I guess it treats the
>> while(0) as a separate statement completely.
> 
>> The correct fix is, AFAICS, to remove the while(0).
> 
> Absolutely not!  The reason for using do/while in this sort of situation
> is to make sure that the "if" can't get matched up to an "else" in code
> following the macro.  Without do/while this macro will be a loaded
> foot-gun.

Oh, didn't think of that. I just thought of the braces part, which was
"solved" by if. Thanks for clearing that up.

Ok, will add back do/while instead.

//Magnus



Re: pg_hba options parsing

От
Magnus Hagander
Дата:
Magnus Hagander wrote:
> 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.

Applied with suggested changes.

//Magnus