Re: Parsing of pg_hba.conf and authentication inconsistencies - Mailing list pgsql-hackers

From Magnus Hagander
Subject Re: Parsing of pg_hba.conf and authentication inconsistencies
Date
Msg-id 48A6F698.50808@hagander.net
Whole thread Raw
In response to Re: Parsing of pg_hba.conf and authentication inconsistencies  (Bruce Momjian <bruce@momjian.us>)
Responses Re: Parsing of pg_hba.conf and authentication inconsistencies  (Tom Lane <tgl@sss.pgh.pa.us>)
Re: Parsing of pg_hba.conf and authentication inconsistencies  ("Brendan Jurd" <direvus@gmail.com>)
List pgsql-hackers
Bruce Momjian wrote:
> Magnus Hagander wrote:
>>> To address Magnus' specific question, right now we store the pg_hba.conf
>>> tokens as strings in the postmaster.  I am fine with storing them in a
>>> more native format and throwing errors for values that don't convert.
>>> What would concern me is calling lots of 3rd party libraries from the
>>> postmaster to validate items.
>> If I was unclear about that, that part was never part of what I
>> proposed. I'm only talking aobut parsing the syntax. The only external
>> calls in the code there now is the getaddrinfo calls to convert the IPs,
>> IIRC.
>
> That seems safe to me.  The use of strings for the pg_hba.conf content
> was only for convenience;  I can see the advantage of using a more
> natural format.

Attached is the patch I have so far. The only "extra" it adds over today
is that it allows the use of "ident" authentication without explicitly
specifying "sameuser" when you want that.

Other than that, it moves code around to do the parsing in the
postmaster and the maching in the backend. This means that now if there
is a syntax error in the file on a reload, we just keep the old file
around still letting people log into the database. If there is a syntax
error on server startup, it's FATAL of course, since we can't run
without any kind of pg_hba.

It also changes a couple of error cases to explicitly state that support
for a certain auth method isn't compiled in, rather than just call it a
syntax error.

Comments?

//Magnus
Index: src/backend/libpq/auth.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/libpq/auth.c,v
retrieving revision 1.167
diff -c -r1.167 auth.c
*** src/backend/libpq/auth.c    1 Aug 2008 11:41:12 -0000    1.167
--- src/backend/libpq/auth.c    16 Aug 2008 15:44:20 -0000
***************
*** 211,217 ****
      if (status == STATUS_EOF)
          proc_exit(0);

!     switch (port->auth_method)
      {
          case uaReject:
              errstr = gettext_noop("authentication failed for user \"%s\": host rejected");
--- 211,217 ----
      if (status == STATUS_EOF)
          proc_exit(0);

!     switch (port->hba->auth_method)
      {
          case uaReject:
              errstr = gettext_noop("authentication failed for user \"%s\": host rejected");
***************
*** 279,285 ****
                   errmsg("missing or erroneous pg_hba.conf file"),
                   errhint("See server log for details.")));

!     switch (port->auth_method)
      {
          case uaReject:

--- 279,285 ----
                   errmsg("missing or erroneous pg_hba.conf file"),
                   errhint("See server log for details.")));

!     switch (port->hba->auth_method)
      {
          case uaReject:

***************
*** 1761,1767 ****
  /*
   *    Determine the username of the initiator of the connection described
   *    by "port".    Then look in the usermap file under the usermap
!  *    port->auth_arg and see if that user is equivalent to Postgres user
   *    port->user.
   *
   *    Return STATUS_OK if yes, STATUS_ERROR if no match (or couldn't get info).
--- 1761,1767 ----
  /*
   *    Determine the username of the initiator of the connection described
   *    by "port".    Then look in the usermap file under the usermap
!  *    port->hba->usermap and see if that user is equivalent to Postgres user
   *    port->user.
   *
   *    Return STATUS_OK if yes, STATUS_ERROR if no match (or couldn't get info).
***************
*** 1799,1805 ****
              (errmsg("Ident protocol identifies remote user as \"%s\"",
                      ident_user)));

!     if (check_ident_usermap(port->auth_arg, port->user_name, ident_user))
          return STATUS_OK;
      else
          return STATUS_ERROR;
--- 1799,1805 ----
              (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;
Index: src/backend/libpq/crypt.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/libpq/crypt.c,v
retrieving revision 1.74
diff -c -r1.74 crypt.c
*** src/backend/libpq/crypt.c    1 Jan 2008 19:45:49 -0000    1.74
--- src/backend/libpq/crypt.c    16 Aug 2008 15:44:20 -0000
***************
*** 54,60 ****
          return STATUS_ERROR;

      /* We can't do crypt with MD5 passwords */
!     if (isMD5(shadow_pass) && port->auth_method == uaCrypt)
      {
          ereport(LOG,
                  (errmsg("cannot use authentication method \"crypt\" because password is MD5-encrypted")));
--- 54,60 ----
          return STATUS_ERROR;

      /* We can't do crypt with MD5 passwords */
!     if (isMD5(shadow_pass) && port->hba->auth_method == uaCrypt)
      {
          ereport(LOG,
                  (errmsg("cannot use authentication method \"crypt\" because password is MD5-encrypted")));
***************
*** 65,71 ****
       * Compare with the encrypted or plain password depending on the
       * authentication method being used for this connection.
       */
!     switch (port->auth_method)
      {
          case uaMD5:
              crypt_pwd = palloc(MD5_PASSWD_LEN + 1);
--- 65,71 ----
       * Compare with the encrypted or plain password depending on the
       * authentication method being used for this connection.
       */
!     switch (port->hba->auth_method)
      {
          case uaMD5:
              crypt_pwd = palloc(MD5_PASSWD_LEN + 1);
***************
*** 155,161 ****
          }
      }

!     if (port->auth_method == uaMD5)
          pfree(crypt_pwd);
      if (crypt_client_pass != client_pass)
          pfree(crypt_client_pass);
--- 155,161 ----
          }
      }

!     if (port->hba->auth_method == uaMD5)
          pfree(crypt_pwd);
      if (crypt_client_pass != client_pass)
          pfree(crypt_client_pass);
Index: src/backend/libpq/hba.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/libpq/hba.c,v
retrieving revision 1.166
diff -c -r1.166 hba.c
*** src/backend/libpq/hba.c    1 Aug 2008 09:09:49 -0000    1.166
--- src/backend/libpq/hba.c    16 Aug 2008 15:44:20 -0000
***************
*** 41,48 ****

  #define MAX_TOKEN    256

  /*
!  * These variables hold the pre-parsed contents of the hba and ident
   * configuration files, as well as the flat auth file.
   * Each is a list of sublists, one sublist for
   * each (non-empty, non-comment) line of the file.    Each sublist's
--- 41,51 ----

  #define MAX_TOKEN    256

+ /* pre-parsed content of HBA config file */
+ static List *parsed_hba_lines = NIL;
+
  /*
!  * These variables hold the pre-parsed contents of the ident
   * configuration files, as well as the flat auth file.
   * Each is a list of sublists, one sublist for
   * each (non-empty, non-comment) line of the file.    Each sublist's
***************
*** 52,61 ****
   * one token, since blank lines are not entered in the data structure.
   */

- /* pre-parsed content of HBA config file and corresponding line #s */
- static List *hba_lines = NIL;
- static List *hba_line_nums = NIL;
-
  /* pre-parsed content of ident usermap file and corresponding line #s */
  static List *ident_lines = NIL;
  static List *ident_line_nums = NIL;
--- 55,60 ----
***************
*** 566,696 ****


  /*
!  *    Scan the rest of a host record (after the mask field)
!  *    and return the interpretation of it as *userauth_p, *auth_arg_p, and
!  *    *error_p.  *line_item points to the next token of the line, and is
!  *    advanced over successfully-read tokens.
   */
! static void
! parse_hba_auth(ListCell **line_item, UserAuth *userauth_p,
!                char **auth_arg_p, bool *error_p)
! {
!     char       *token;
!
!     *auth_arg_p = NULL;
!
!     if (!*line_item)
!     {
!         *error_p = true;
!         return;
!     }
!
!     token = lfirst(*line_item);
!     if (strcmp(token, "trust") == 0)
!         *userauth_p = uaTrust;
!     else if (strcmp(token, "ident") == 0)
!         *userauth_p = uaIdent;
!     else if (strcmp(token, "password") == 0)
!         *userauth_p = uaPassword;
!     else if (strcmp(token, "krb5") == 0)
!         *userauth_p = uaKrb5;
!     else if (strcmp(token, "gss") == 0)
!         *userauth_p = uaGSS;
!     else if (strcmp(token, "sspi") == 0)
!         *userauth_p = uaSSPI;
!     else if (strcmp(token, "reject") == 0)
!         *userauth_p = uaReject;
!     else if (strcmp(token, "md5") == 0)
!         *userauth_p = uaMD5;
!     else if (strcmp(token, "crypt") == 0)
!         *userauth_p = uaCrypt;
! #ifdef USE_PAM
!     else if (strcmp(token, "pam") == 0)
!         *userauth_p = uaPAM;
! #endif
! #ifdef USE_LDAP
!     else if (strcmp(token, "ldap") == 0)
!         *userauth_p = uaLDAP;
! #endif
!     else
!     {
!         *error_p = true;
!         return;
!     }
!     *line_item = lnext(*line_item);
!
!     /* Get the authentication argument token, if any */
!     if (*line_item)
!     {
!         token = lfirst(*line_item);
!         *auth_arg_p = pstrdup(token);
!         *line_item = lnext(*line_item);
!         /* If there is more on the line, it is an error */
!         if (*line_item)
!             *error_p = true;
!     }
! }
!
!
! /*
!  *    Process one line from the hba config file.
!  *
!  *    See if it applies to a connection from a host with IP address port->raddr
!  *    to a database named port->database.  If so, return *found_p true
!  *    and fill in the auth arguments into the appropriate port fields.
!  *    If not, leave *found_p as it was.  If the record has a syntax error,
!  *    return *error_p true, after issuing a message to the log.  If no error,
!  *    leave *error_p as it was.
!  */
! static void
! parse_hba(List *line, int line_num, hbaPort *port,
!           bool *found_p, bool *error_p)
  {
      char       *token;
-     char       *db;
-     char       *role;
      struct addrinfo *gai_result;
      struct addrinfo hints;
      int            ret;
-     struct sockaddr_storage addr;
-     struct sockaddr_storage mask;
      char       *cidr_slash;
      ListCell   *line_item;

      line_item = list_head(line);
      /* Check the record type. */
      token = lfirst(line_item);
      if (strcmp(token, "local") == 0)
      {
!         /* Get the database. */
!         line_item = lnext(line_item);
!         if (!line_item)
!             goto hba_syntax;
!         db = lfirst(line_item);
!
!         /* Get the role. */
!         line_item = lnext(line_item);
!         if (!line_item)
!             goto hba_syntax;
!         role = lfirst(line_item);
!
!         line_item = lnext(line_item);
!         if (!line_item)
!             goto hba_syntax;
!
!         /* Read the rest of the line. */
!         parse_hba_auth(&line_item, &port->auth_method,
!                        &port->auth_arg, error_p);
!         if (*error_p)
!             goto hba_syntax;
!
!         /* Disallow auth methods that always need TCP/IP sockets to work */
!         if (port->auth_method == uaKrb5)
!             goto hba_syntax;
!
!         /* Does not match if connection isn't AF_UNIX */
!         if (!IS_AF_UNIX(port->raddr.addr.ss_family))
!             return;
      }
      else if (strcmp(token, "host") == 0
               || strcmp(token, "hostssl") == 0
--- 565,593 ----


  /*
!  * Parse one line in the hba config file and store the result in
!  * a HbaLine structure.
   */
! static bool
! parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  {
      char       *token;
      struct addrinfo *gai_result;
      struct addrinfo hints;
      int            ret;
      char       *cidr_slash;
+     char       *unsupauth;
      ListCell   *line_item;

      line_item = list_head(line);
+
+     parsedline->linenumber = line_num;
+
      /* Check the record type. */
      token = lfirst(line_item);
      if (strcmp(token, "local") == 0)
      {
!         parsedline->conntype = ctLocal;
      }
      else if (strcmp(token, "host") == 0
               || strcmp(token, "hostssl") == 0
***************
*** 700,713 ****
          if (token[4] == 's')    /* "hostssl" */
          {
  #ifdef USE_SSL
!             /* Record does not match if we are not on an SSL connection */
!             if (!port->ssl)
!                 return;
!
!             /* Placeholder to require specific SSL level, perhaps? */
!             /* Or a client certificate */
!
!             /* Since we were on SSL, proceed as with normal 'host' mode */
  #else
              /* We don't accept this keyword at all if no SSL support */
              goto hba_syntax;
--- 597,603 ----
          if (token[4] == 's')    /* "hostssl" */
          {
  #ifdef USE_SSL
!             parsedline->conntype = ctHostSSL;
  #else
              /* We don't accept this keyword at all if no SSL support */
              goto hba_syntax;
***************
*** 716,744 ****
  #ifdef USE_SSL
          else if (token[4] == 'n')        /* "hostnossl" */
          {
!             /* Record does not match if we are on an SSL connection */
!             if (port->ssl)
!                 return;
          }
  #endif

!         /* Get the database. */
!         line_item = lnext(line_item);
!         if (!line_item)
!             goto hba_syntax;
!         db = lfirst(line_item);

!         /* Get the role. */
!         line_item = lnext(line_item);
!         if (!line_item)
!             goto hba_syntax;
!         role = lfirst(line_item);

          /* Read the IP address field. (with or without CIDR netmask) */
          line_item = lnext(line_item);
          if (!line_item)
              goto hba_syntax;
!         token = lfirst(line_item);

          /* Check if it has a CIDR suffix and if so isolate it */
          cidr_slash = strchr(token, '/');
--- 606,642 ----
  #ifdef USE_SSL
          else if (token[4] == 'n')        /* "hostnossl" */
          {
!             parsedline->conntype = ctHostNoSSL;
          }
  #endif
+         else
+         {
+             /* "host", or "hostnossl" and SSL support not built in */
+             parsedline->conntype = ctHost;
+         }
+     } /* record type */
+     else
+         goto hba_syntax;

!     /* Get the database. */
!     line_item = lnext(line_item);
!     if (!line_item)
!         goto hba_syntax;
!     parsedline->database = pstrdup(lfirst(line_item));

!     /* Get the role. */
!     line_item = lnext(line_item);
!     if (!line_item)
!         goto hba_syntax;
!     parsedline->role = pstrdup(lfirst(line_item));

+     if (parsedline->conntype != ctLocal)
+     {
          /* Read the IP address field. (with or without CIDR netmask) */
          line_item = lnext(line_item);
          if (!line_item)
              goto hba_syntax;
!         token = pstrdup(lfirst(line_item));

          /* Check if it has a CIDR suffix and if so isolate it */
          cidr_slash = strchr(token, '/');
***************
*** 760,768 ****
          {
              ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                errmsg("invalid IP address \"%s\" in file \"%s\" line %d: %s",
!                       token, HbaFileName, line_num,
!                       gai_strerror(ret))));
              if (cidr_slash)
                  *cidr_slash = '/';
              if (gai_result)
--- 658,666 ----
          {
              ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                      errmsg("invalid IP address \"%s\" in file \"%s\" line %d: %s",
!                             token, HbaFileName, line_num,
!                             gai_strerror(ret))));
              if (cidr_slash)
                  *cidr_slash = '/';
              if (gai_result)
***************
*** 773,786 ****
          if (cidr_slash)
              *cidr_slash = '/';

!         memcpy(&addr, gai_result->ai_addr, gai_result->ai_addrlen);
          pg_freeaddrinfo_all(hints.ai_family, gai_result);

          /* Get the netmask */
          if (cidr_slash)
          {
!             if (pg_sockaddr_cidr_mask(&mask, cidr_slash + 1,
!                                       addr.ss_family) < 0)
                  goto hba_syntax;
          }
          else
--- 671,684 ----
          if (cidr_slash)
              *cidr_slash = '/';

!         memcpy(&parsedline->addr, gai_result->ai_addr, gai_result->ai_addrlen);
          pg_freeaddrinfo_all(hints.ai_family, gai_result);

          /* Get the netmask */
          if (cidr_slash)
          {
!             if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
!                                       parsedline->addr.ss_family) < 0)
                  goto hba_syntax;
          }
          else
***************
*** 796,813 ****
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                   errmsg("invalid IP mask \"%s\" in file \"%s\" line %d: %s",
!                          token, HbaFileName, line_num,
!                          gai_strerror(ret))));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  goto hba_other_error;
              }

!             memcpy(&mask, gai_result->ai_addr, gai_result->ai_addrlen);
              pg_freeaddrinfo_all(hints.ai_family, gai_result);

!             if (addr.ss_family != mask.ss_family)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
--- 694,711 ----
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                          errmsg("invalid IP mask \"%s\" in file \"%s\" line %d: %s",
!                                 token, HbaFileName, line_num,
!                                 gai_strerror(ret))));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  goto hba_other_error;
              }

!             memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen);
              pg_freeaddrinfo_all(hints.ai_family, gai_result);

!             if (parsedline->addr.ss_family != parsedline->mask.ss_family)
              {
                  ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
***************
*** 816,877 ****
                  goto hba_other_error;
              }
          }

!         if (addr.ss_family != port->raddr.addr.ss_family)
          {
!             /*
!              * Wrong address family.  We allow only one case: if the file has
!              * IPv4 and the port is IPv6, promote the file address to IPv6 and
!              * try to match that way.
!              */
! #ifdef HAVE_IPV6
!             if (addr.ss_family == AF_INET &&
!                 port->raddr.addr.ss_family == AF_INET6)
              {
!                 pg_promote_v4_to_v6_addr(&addr);
!                 pg_promote_v4_to_v6_mask(&mask);
              }
              else
- #endif   /* HAVE_IPV6 */
              {
!                 /* Line doesn't match client port, so ignore it. */
!                 return;
              }
          }
-
-         /* Ignore line if client port is not in the matching addr range. */
-         if (!pg_range_sockaddr(&port->raddr.addr, &addr, &mask))
-             return;
-
-         /* Read the rest of the line. */
-         line_item = lnext(line_item);
-         if (!line_item)
-             goto hba_syntax;
-         parse_hba_auth(&line_item, &port->auth_method,
-                        &port->auth_arg, error_p);
-         if (*error_p)
-             goto hba_syntax;
      }
!     else
!         goto hba_syntax;
!
!     /* Does the entry match database and role? */
!     if (!check_db(port->database_name, port->user_name, db))
!         return;
!     if (!check_role(port->user_name, role))
!         return;
!
!     /* Success */
!     *found_p = true;
!     return;

  hba_syntax:
      if (line_item)
          ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
!               errmsg("invalid entry in file \"%s\" at line %d, token \"%s\"",
!                      HbaFileName, line_num,
!                      (char *) lfirst(line_item))));
      else
          ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
--- 714,841 ----
                  goto hba_other_error;
              }
          }
+     } /* != ctLocal */
+
+     /* Get the authentication method */
+     line_item = lnext(line_item);
+     if (!line_item)
+         goto hba_syntax;
+     token = lfirst(line_item);
+
+     unsupauth = NULL;
+     if (strcmp(token, "trust") == 0)
+         parsedline->auth_method = uaTrust;
+     else if (strcmp(token, "ident") == 0)
+         parsedline->auth_method = uaIdent;
+     else if (strcmp(token, "password") == 0)
+         parsedline->auth_method = uaPassword;
+     else if (strcmp(token, "krb5") == 0)
+ #ifdef KRB5
+         parsedline->auth_method = uaKrb5;
+ #else
+         unsupauth = "krb5";
+ #endif
+     else if (strcmp(token, "gss") == 0)
+ #ifdef ENABLE_GSS
+         parsedline->auth_method = uaGSS;
+ #else
+         unsupauth = "gss";
+ #endif
+     else if (strcmp(token, "sspi") == 0)
+ #ifdef ENABLE_SSPI
+         parsedline->auth_method = uaSSPI;
+ #else
+         unsupauth = "sspi";
+ #endif
+     else if (strcmp(token, "reject") == 0)
+         parsedline->auth_method = uaReject;
+     else if (strcmp(token, "md5") == 0)
+         parsedline->auth_method = uaMD5;
+     else if (strcmp(token, "crypt") == 0)
+         parsedline->auth_method = uaCrypt;
+     else if (strcmp(token, "pam") == 0)
+ #ifdef USE_PAM
+         parsedline->auth_method = uaPAM;
+ #else
+         unsupauth = "pam";
+ #endif
+     else if (strcmp(token, "ldap") == 0)
+ #ifdef USE_LDAP
+         parsedline->auth_method = uaLDAP;
+ #else
+         unsupauth = "ldap";
+ #endif
+     else
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                  errmsg("invalid authentication method \"%s\" in file \"%s\" line %d",
+                         token, HbaFileName, line_num)));
+         goto hba_other_error;
+     }
+
+     if (unsupauth)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                  errmsg("invalid authentication method \"%s\" in file \"%s\" line %d: not supported on this
platform",
+                         token, HbaFileName, line_num)));
+         goto hba_other_error;
+     }
+
+     /* Invalid authentication combinations */
+     if (parsedline->conntype == ctLocal &&
+         parsedline->auth_method == uaKrb5)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                  errmsg("krb5 authentication is not supported on local sockets in file \"%s\" line %d",
+                         HbaFileName, line_num)));
+         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;

  hba_syntax:
      if (line_item)
          ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                  errmsg("invalid entry in file \"%s\" at line %d, token \"%s\"",
!                         HbaFileName, line_num,
!                         (char *) lfirst(line_item))));
      else
          ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
***************
*** 880,886 ****

      /* Come here if suitable message already logged */
  hba_other_error:
!     *error_p = true;
  }


--- 844,850 ----

      /* Come here if suitable message already logged */
  hba_other_error:
!     return false;
  }


***************
*** 891,918 ****
  static bool
  check_hba(hbaPort *port)
  {
-     bool        found_entry = false;
-     bool        error = false;
      ListCell   *line;
!     ListCell   *line_num;

!     forboth(line, hba_lines, line_num, hba_line_nums)
      {
!         parse_hba(lfirst(line), lfirst_int(line_num),
!                   port, &found_entry, &error);
!         if (found_entry || error)
!             break;
!     }

!     if (!error)
!     {
!         /* If no matching entry was found, synthesize 'reject' entry. */
!         if (!found_entry)
!             port->auth_method = uaReject;
          return true;
      }
!     else
!         return false;
  }


--- 855,950 ----
  static bool
  check_hba(hbaPort *port)
  {
      ListCell   *line;
!     HbaLine       *hba;

!     foreach(line, parsed_hba_lines)
      {
!         hba = (HbaLine *) lfirst(line);

!         /* Check connection type */
!         if (hba->conntype == ctLocal)
!         {
!             if (!IS_AF_UNIX(port->raddr.addr.ss_family))
!                 continue;
!         }
!         else
!         {
!             if (IS_AF_UNIX(port->raddr.addr.ss_family))
!                 continue;
!
!             /* Check SSL state */
! #ifdef USE_SSL
!             if (port->ssl)
!             {
!                 /* Connection is SSL, match both "host" and "hostssl" */
!                 if (hba->conntype == ctHostNoSSL)
!                     continue;
!             }
!             else
!             {
!                 /* Connection is not SSL, match both "host" and "hostnossl" */
!                 if (hba->conntype == ctHostSSL)
!                     continue;
!             }
! #else
!             /* No SSL support, so reject "hostssl" lines */
!             if (hba->conntype == ctHostSSL)
!                 continue;
! #endif
!
!             /* Check IP address */
!             if (port->raddr.addr.ss_family == hba->addr.ss_family)
!             {
!                 if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask))
!                     continue;
!             }
! #ifdef HAVE_IPV6
!             else  if (hba->addr.ss_family == AF_INET &&
!                       port->raddr.addr.ss_family == AF_INET6)
!             {
!                 /*
!                  * Wrong address family.  We allow only one case: if the file has
!                  * IPv4 and the port is IPv6, promote the file address to IPv6 and
!                  * try to match that way.
!                  */
!                 struct sockaddr_storage addrcopy, maskcopy;
!                 memcpy(&addrcopy, &hba->addr, sizeof(addrcopy));
!                 memcpy(&maskcopy, &hba->mask, sizeof(maskcopy));
!                 pg_promote_v4_to_v6_addr(&addrcopy);
!                 pg_promote_v4_to_v6_mask(&maskcopy);
!
!                 if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy))
!                     continue;
!             }
! #endif /* HAVE_IPV6 */
!             else
!                 /* Wrong address family, no IPV6 */
!                 continue;
!         } /* != ctLocal */
!
!         /* Check database and role */
!         if (!check_db(port->database_name, port->user_name, hba->database))
!             continue;
!
!         if (!check_role(port->user_name, hba->role))
!             continue;
!
!         /* Found a record that matched! */
!         port->hba = hba;
          return true;
      }
!
!     /* If no matching entry was found, synthesize 'reject' entry. */
!     hba = palloc0(sizeof(HbaLine));
!     hba->auth_method = uaReject;
!     port->hba = hba;
!     return true;
!
!     /* XXX:
!      * Return false only happens if we have a parsing error, which we can
!      * no longer have (parsing now in postmaster). Consider changing API.
!      */
  }


***************
*** 967,983 ****
      }
  }


  /*
!  * Read the config file and create a List of Lists of tokens in the file.
   */
! void
  load_hba(void)
  {
      FILE       *file;
!
!     if (hba_lines || hba_line_nums)
!         free_lines(&hba_lines, &hba_line_nums);

      file = AllocateFile(HbaFileName, "r");
      /* Failure is fatal since with no HBA entries we can do nothing... */
--- 999,1050 ----
      }
  }

+ /*
+  * Free the contents of a hba record
+  */
+ static void
+ free_hba_record(HbaLine *record)
+ {
+     if (record->database)
+         pfree(record->database);
+     if (record->role)
+         pfree(record->role);
+     if (record->auth_arg)
+         pfree(record->auth_arg);
+ }

  /*
!  * Free all records on the parsed HBA list
   */
! static void
! clean_hba_list(List *lines)
! {
!     ListCell    *line;
!
!     foreach(line, lines)
!     {
!         HbaLine *parsed = (HbaLine *)lfirst(line);
!         if (parsed)
!             free_hba_record(parsed);
!     }
!     list_free(lines);
! }
!
! /*
!  * Read the config file and create a List of HbaLine records for the contents.
!  *
!  * The configuration is read into a temporary list, and if any parse error occurs
!  * the old list is kept in place and false is returned. Only if the whole file
!  * parses Ok is the list replaced, and the function returns true.
!  */
! bool
  load_hba(void)
  {
      FILE       *file;
!     List *hba_lines = NIL;
!     List *hba_line_nums = NIL;
!     ListCell   *line, *line_num;
!     List *new_parsed_lines = NIL;

      file = AllocateFile(HbaFileName, "r");
      /* Failure is fatal since with no HBA entries we can do nothing... */
***************
*** 989,994 ****
--- 1056,1090 ----

      tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums);
      FreeFile(file);
+
+     /* Now parse all the lines */
+     forboth(line, hba_lines, line_num, hba_line_nums)
+     {
+         HbaLine *newline;
+
+         newline = palloc0(sizeof(HbaLine));
+
+         if (!parse_hba_line(lfirst(line), lfirst_int(line_num), newline))
+         {
+             /* Parse error in the file, so bail out */
+             free_hba_record(newline);
+             pfree(newline);
+             clean_hba_list(new_parsed_lines);
+             /* Error has already been reported in the parsing function */
+             return false;
+         }
+
+         new_parsed_lines = lappend(new_parsed_lines, newline);
+     }
+
+     /* Loaded new file successfully, replace the one we use */
+     clean_hba_list(parsed_hba_lines);
+     parsed_hba_lines = new_parsed_lines;
+
+     /* Free the temporary lists */
+     free_lines(&hba_lines, &hba_line_nums);
+
+     return true;
  }

  /*
***************
*** 1100,1106 ****
   *    See if the user with ident username "ident_user" is allowed to act
   *    as Postgres user "pgrole" according to usermap "usermap_name".
   *
!  *    Special case: For usermap "samerole", don't look in the usermap
   *    file.  That's an implied map where "pgrole" must be identical to
   *    "ident_user" in order to be authorized.
   *
--- 1196,1203 ----
   *    See if the user with ident username "ident_user" is allowed to act
   *    as Postgres user "pgrole" according to usermap "usermap_name".
   *
!  *  Special case: Usermap NULL, equivalent to what was previously called
!  *  "sameuser" or "samerole", don't look in the usermap
   *    file.  That's an implied map where "pgrole" must be identical to
   *    "ident_user" in order to be authorized.
   *
***************
*** 1116,1129 ****

      if (usermap_name == NULL || usermap_name[0] == '\0')
      {
-         ereport(LOG,
-                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
-            errmsg("cannot use Ident authentication without usermap field")));
-         found_entry = false;
-     }
-     else if (strcmp(usermap_name, "sameuser\n") == 0 ||
-              strcmp(usermap_name, "samerole\n") == 0)
-     {
          if (strcmp(pg_role, ident_user) == 0)
              found_entry = true;
          else
--- 1213,1218 ----
Index: src/backend/postmaster/postmaster.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v
retrieving revision 1.561
diff -c -r1.561 postmaster.c
*** src/backend/postmaster/postmaster.c    26 Jun 2008 02:47:19 -0000    1.561
--- src/backend/postmaster/postmaster.c    16 Aug 2008 15:44:20 -0000
***************
*** 888,894 ****
      /*
       * Load configuration files for client authentication.
       */
!     load_hba();
      load_ident();

      /*
--- 888,902 ----
      /*
       * Load configuration files for client authentication.
       */
!     if (!load_hba())
!     {
!         /*
!          * It makes no sense continue if we fail to load the HBA file, since
!          * there is no way to connect to the database in this case.
!          */
!         ereport(FATAL,
!                 (errmsg("could not load pg_hba.conf")));
!     }
      load_ident();

      /*
***************
*** 1926,1932 ****
          /* PgStatPID does not currently need SIGHUP */

          /* Reload authentication config files too */
!         load_hba();
          load_ident();

  #ifdef EXEC_BACKEND
--- 1934,1943 ----
          /* PgStatPID does not currently need SIGHUP */

          /* Reload authentication config files too */
!         if (!load_hba())
!             ereport(WARNING,
!                     (errmsg("pg_hba.conf not reloaded")));
!
          load_ident();

  #ifdef EXEC_BACKEND
***************
*** 3080,3086 ****
                                                ALLOCSET_DEFAULT_MAXSIZE);
      MemoryContextSwitchTo(PostmasterContext);

!     load_hba();
      load_ident();
      load_role();
  #endif
--- 3091,3105 ----
                                                ALLOCSET_DEFAULT_MAXSIZE);
      MemoryContextSwitchTo(PostmasterContext);

!     if (!load_hba())
!     {
!         /*
!          * It makes no sense continue if we fail to load the HBA file, since
!          * there is no way to connect to the database in this case.
!          */
!         ereport(FATAL,
!                 (errmsg("could not load pg_hba.conf")));
!     }
      load_ident();
      load_role();
  #endif
Index: src/include/libpq/hba.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/libpq/hba.h,v
retrieving revision 1.48
diff -c -r1.48 hba.h
*** src/include/libpq/hba.h    1 Aug 2008 09:09:48 -0000    1.48
--- src/include/libpq/hba.h    16 Aug 2008 15:44:20 -0000
***************
*** 33,42 ****
  #endif
  } UserAuth;

  typedef struct Port hbaPort;

  extern List **get_role_line(const char *role);
! extern void load_hba(void);
  extern void load_ident(void);
  extern void load_role(void);
  extern int    hba_getauthmethod(hbaPort *port);
--- 33,63 ----
  #endif
  } UserAuth;

+ typedef enum ConnType
+ {
+     ctLocal,
+     ctHost,
+     ctHostSSL,
+     ctHostNoSSL
+ } ConnType;
+
+ typedef struct
+ {
+     int            linenumber;
+     ConnType    conntype;
+     char       *database;
+     char       *role;
+     struct sockaddr_storage addr;
+     struct sockaddr_storage mask;
+     UserAuth    auth_method;
+     char       *usermap;
+     char       *auth_arg;
+ } HbaLine;
+
  typedef struct Port hbaPort;

  extern List **get_role_line(const char *role);
! extern bool load_hba(void);
  extern void load_ident(void);
  extern void load_role(void);
  extern int    hba_getauthmethod(hbaPort *port);
Index: src/include/libpq/libpq-be.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/libpq/libpq-be.h,v
retrieving revision 1.66
diff -c -r1.66 libpq-be.h
*** src/include/libpq/libpq-be.h    26 Apr 2008 22:47:40 -0000    1.66
--- src/include/libpq/libpq-be.h    16 Aug 2008 15:44:20 -0000
***************
*** 121,128 ****
      /*
       * Information that needs to be held during the authentication cycle.
       */
!     UserAuth    auth_method;
!     char       *auth_arg;
      char        md5Salt[4];        /* Password salt */
      char        cryptSalt[2];    /* Password salt */

--- 121,127 ----
      /*
       * Information that needs to be held during the authentication cycle.
       */
!     HbaLine       *hba;
      char        md5Salt[4];        /* Password salt */
      char        cryptSalt[2];    /* Password salt */


pgsql-hackers by date:

Previous
From: Magnus Hagander
Date:
Subject: [Fwd: [COMMITTERS] pgsql: probes.h is generated from probes.d, not pg_trace.d.]
Next
From: Tom Lane
Date:
Subject: Re: Parsing of pg_hba.conf and authentication inconsistencies