Hi,

On 9/17/22 8:53 AM, Michael Paquier wrote:
On Fri, Sep 16, 2022 at 06:24:07PM +0200, Drouvot, Bertrand wrote:
The CF bot is failing for Windows (all other tests are green) and only for
the new tap test related to the regular expression on the host name (the
ones on database and role are fine).

The issue is not related to the patch. The issue is that the Windows Cirrus
test does not like when a host name is provided for a "host" entry in
pg_hba.conf (while it works fine when a CIDR is provided).

You can see an example in [1] where the only change is to replace the CIDR
by "localhost" in 002_scram.pl. As you can see the Cirrus tests are failing
on Windows only (its log file is here [2]).

I'll look at this "Windows" related issue but would appreciate any
guidance/help if someone has experience in this area on windows.

I recall that being able to do a reverse lookup of a hostname on
Windows for localhost requires a few extra setup steps as that's not
guaranteed to be set in all environments by default, which is why we
go at great length to use 127.0.0.1 in the TAP test setup for example
(see Cluster.pm).  Looking at your patch, the goal is to test the
mapping of regular expression for host names, user names and database
names.  If the first case is not guaranteed, my guess is that it is
fine to skip this portion of the tests on Windows.

Thanks for looking at it!

That sounds reasonable, v3 attached is skipping the regular expression tests for the hostname on Windows.



While reading the patch, I am a bit confused about token_regcomp() and
token_regexec().  It would help the review a lot if these were
documented with proper comments, even if these act roughly as wrappers
for pg_regexec() and pg_regcomp().

Fully agree, comments were missing. They've been added in v3 attached.

Regards,

--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index c6f1b70fd3..28343445be 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -235,8 +235,9 @@ hostnogssenc  <replaceable>database</replaceable>  
<replaceable>user</replaceabl
        logical replication connections do specify it.
        Otherwise, this is the name of
        a specific <productname>PostgreSQL</productname> database.
-       Multiple database names can be supplied by separating them with
-       commas.  A separate file containing database names can be specified by
+       Multiple database names and/or regular expressions preceded by 
<literal>/</literal>
+       can be supplied by separating them with commas.
+       A separate file containing database names can be specified by
        preceding the file name with <literal>@</literal>.
       </para>
      </listitem>
@@ -249,7 +250,8 @@ hostnogssenc  <replaceable>database</replaceable>  
<replaceable>user</replaceabl
        Specifies which database user name(s) this record
        matches. The value <literal>all</literal> specifies that it
        matches all users.  Otherwise, this is either the name of a specific
-       database user, or a group name preceded by <literal>+</literal>.
+       database user, a regular expression preceded by <literal>/</literal>
+       or a group name preceded by <literal>+</literal>.
        (Recall that there is no real distinction between users and groups
        in <productname>PostgreSQL</productname>; a <literal>+</literal> mark 
really means
        <quote>match any of the roles that are directly or indirectly members
@@ -258,7 +260,8 @@ hostnogssenc  <replaceable>database</replaceable>  
<replaceable>user</replaceabl
        considered to be a member of a role if they are explicitly a member
        of the role, directly or indirectly, and not just by virtue of
        being a superuser.
-       Multiple user names can be supplied by separating them with commas.
+       Multiple user names and/or regular expressions preceded by 
<literal>/</literal>
+       can be supplied by separating them with commas.
        A separate file containing user names can be specified by preceding the
        file name with <literal>@</literal>.
       </para>
@@ -270,8 +273,9 @@ hostnogssenc  <replaceable>database</replaceable>  
<replaceable>user</replaceabl
      <listitem>
       <para>
        Specifies the client machine address(es) that this record
-       matches.  This field can contain either a host name, an IP
-       address range, or one of the special key words mentioned below.
+       matches.  This field can contain either a host name, a regular 
expression
+       preceded by <literal>/</literal> representing host names, an IP address 
range,
+       or one of the special key words mentioned below.
       </para>
 
       <para>
@@ -785,16 +789,18 @@ host    all             all             192.168.12.10/32  
      gss
 # TYPE  DATABASE        USER            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
+# If these are the only four lines for local connections, they will
 # allow local users to connect only to their own databases (databases
-# with the same name as their database user name) except for administrators
-# and members of role "support", who can connect to all databases.  The file
-# $PGDATA/admins contains a list of names of administrators.  Passwords
+# with the same name as their database user name) except for administrators,
+# users finishing with "helpdesk" and members of role "support",
+# who can connect to all databases.
+# The file$PGDATA/admins contains a list of names of administrators.  Passwords
 # are required in all cases.
 #
 # TYPE  DATABASE        USER            ADDRESS                 METHOD
 local   sameuser        all                                     md5
 local   all             @admins                                 md5
+local   all             /^.*helpdesk$                           md5
 local   all             +support                                md5
 
 # The last two lines above can be combined into a single line:
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 4637426d62..89a4fa8b98 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -117,6 +117,9 @@ static List *tokenize_inc_file(List *tokens, const char 
*outer_filename,
                                                           const char 
*inc_filename, int elevel, char **err_msg);
 static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
                                                           int elevel, char 
**err_msg);
+static bool token_regcomp(regex_t *re, char *string, char *filename,
+                                                 int line_num, char **err_msg, 
int elevel);
+static bool token_regexec(const char *match, regex_t *re);
 
 
 /*
@@ -574,13 +577,15 @@ is_member(Oid userid, const char *role)
 }
 
 /*
- * Check AuthToken list for a match to role, allowing group names.
+ * Check AuthToken list for a match to role.
+ * We are allowing group names and regular expressions.
  */
 static bool
-check_role(const char *role, Oid roleid, List *tokens)
+check_role(const char *role, Oid roleid, List *tokens, List *tokens_re)
 {
        ListCell   *cell;
        AuthToken  *tok;
+       int                     re_num = 0;
 
        foreach(cell, tokens)
        {
@@ -590,6 +595,23 @@ check_role(const char *role, Oid roleid, List *tokens)
                        if (is_member(roleid, tok->string + 1))
                                return true;
                }
+               else if (!tok->quoted && tok->string[0] == '/')
+               {
+                       /*
+                        * When tok->string starts with a slash, treat it as a 
regular
+                        * expression.
+                        */
+                       ListCell   *cell_re;
+                       regex_t    *re;
+
+                       cell_re = list_nth_cell(tokens_re, re_num);
+                       re = lfirst(cell_re);
+
+                       if (token_regexec(role, re))
+                               return true;
+
+                       re_num++;
+               }
                else if (token_matches(tok, role) ||
                                 token_is_keyword(tok, "all"))
                        return true;
@@ -601,10 +623,11 @@ check_role(const char *role, Oid roleid, List *tokens)
  * Check to see if db/role combination matches AuthToken list.
  */
 static bool
-check_db(const char *dbname, const char *role, Oid roleid, List *tokens)
+check_db(const char *dbname, const char *role, Oid roleid, List *tokens, List 
*tokens_re)
 {
        ListCell   *cell;
        AuthToken  *tok;
+       int                     re_num = 0;
 
        foreach(cell, tokens)
        {
@@ -633,6 +656,23 @@ check_db(const char *dbname, const char *role, Oid roleid, 
List *tokens)
                }
                else if (token_is_keyword(tok, "replication"))
                        continue;                       /* never match this if 
not walsender */
+               else if (!tok->quoted && tok->string[0] == '/')
+               {
+                       /*
+                        * When tok->string starts with a slash, treat it as a 
regular
+                        * expression.
+                        */
+                       ListCell   *cell_re;
+                       regex_t    *re;
+
+                       cell_re = list_nth_cell(tokens_re, re_num);
+                       re = lfirst(cell_re);
+
+                       if (token_regexec(dbname, re))
+                               return true;
+
+                       re_num++;
+               }
                else if (token_matches(tok, dbname))
                        return true;
        }
@@ -681,7 +721,7 @@ hostname_match(const char *pattern, const char 
*actual_hostname)
  * Check to see if a connecting IP matches a given host name.
  */
 static bool
-check_hostname(hbaPort *port, const char *hostname)
+check_hostname(hbaPort *port, const char *hostname, regex_t re)
 {
        struct addrinfo *gai_result,
                           *gai;
@@ -712,8 +752,17 @@ check_hostname(hbaPort *port, const char *hostname)
                port->remote_hostname = pstrdup(remote_hostname);
        }
 
+       if (hostname[0] == '/')
+       {
+               /*
+                * When hostname starts with a slash, treat it as a regular
+                * expression.
+                */
+               if (!token_regexec(port->remote_hostname, &re))
+                       return false;
+       }
        /* Now see if remote host name matches this pg_hba line */
-       if (!hostname_match(hostname, port->remote_hostname))
+       else if (!hostname_match(hostname, port->remote_hostname))
                return false;
 
        /* If we already verified the forward lookup, we're done */
@@ -939,13 +988,13 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
        struct addrinfo *gai_result;
        struct addrinfo hints;
        int                     ret;
-       char       *cidr_slash;
        char       *unsupauth;
        ListCell   *field;
        List       *tokens;
        ListCell   *tokencell;
        AuthToken  *token;
        HbaLine    *parsedline;
+       char       *cidr_slash = NULL; /* keep compiler quiet */
 
        parsedline = palloc0(sizeof(HbaLine));
        parsedline->linenumber = line_num;
@@ -1049,9 +1098,27 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
                return NULL;
        }
        parsedline->databases = NIL;
+       parsedline->databases_re = NIL;
        tokens = lfirst(field);
        foreach(tokencell, tokens)
        {
+               AuthToken  *tok = lfirst(tokencell);
+
+               if (!tok->quoted && tok->string[0] == '/')
+               {
+                       /*
+                        * When tok->string starts with a slash, treat it as a 
regular
+                        * expression. Pre-compile it.
+                        */
+                       regex_t    *re;
+
+                       re = (regex_t *) palloc(sizeof(regex_t));
+                       if (token_regcomp(re, tok->string + 1, HbaFileName, 
line_num,
+                                                         err_msg, elevel))
+                               parsedline->databases_re = 
lappend(parsedline->databases_re, re);
+                       else
+                               return NULL;
+               }
                parsedline->databases = lappend(parsedline->databases,
                                                                                
copy_auth_token(lfirst(tokencell)));
        }
@@ -1069,9 +1136,27 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
                return NULL;
        }
        parsedline->roles = NIL;
+       parsedline->roles_re = NIL;
        tokens = lfirst(field);
        foreach(tokencell, tokens)
        {
+               AuthToken  *tok = lfirst(tokencell);
+
+               if (!tok->quoted && tok->string[0] == '/')
+               {
+                       /*
+                        * When tok->string starts with a slash, treat it as a 
regular
+                        * expression. Pre-compile it.
+                        */
+                       regex_t    *re;
+
+                       re = (regex_t *) palloc(sizeof(regex_t));
+                       if (token_regcomp(re, tok->string + 1, HbaFileName, 
line_num,
+                                                         err_msg, elevel))
+                               parsedline->roles_re = 
lappend(parsedline->roles_re, re);
+                       else
+                               return NULL;
+               }
                parsedline->roles = lappend(parsedline->roles,
                                                                        
copy_auth_token(lfirst(tokencell)));
        }
@@ -1120,6 +1205,8 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
                }
                else
                {
+                       bool is_regexp = token->string[0] == '/' ? true : false;
+
                        /* IP and netmask are specified */
                        parsedline->ip_cmp_method = ipCmpMask;
 
@@ -1127,9 +1214,12 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
                        str = pstrdup(token->string);
 
                        /* Check if it has a CIDR suffix and if so isolate it */
-                       cidr_slash = strchr(str, '/');
-                       if (cidr_slash)
-                               *cidr_slash = '\0';
+                       if (!is_regexp)
+                       {
+                               cidr_slash = strchr(str, '/');
+                               if (cidr_slash)
+                                       *cidr_slash = '\0';
+                       }
 
                        /* Get the IP address either way */
                        hints.ai_flags = AI_NUMERICHOST;
@@ -1168,7 +1258,7 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
                        pg_freeaddrinfo_all(hints.ai_family, gai_result);
 
                        /* Get the netmask */
-                       if (cidr_slash)
+                       if (cidr_slash && !is_regexp)
                        {
                                if (parsedline->hostname)
                                {
@@ -1199,7 +1289,7 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
                                parsedline->masklen = parsedline->addrlen;
                                pfree(str);
                        }
-                       else if (!parsedline->hostname)
+                       else if (!parsedline->hostname && !is_regexp)
                        {
                                /* Read the mask field. */
                                pfree(str);
@@ -1261,6 +1351,18 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
                                        return NULL;
                                }
                        }
+                       else if (is_regexp)
+                       {
+                               /*
+                                * When token->string starts with a slash, 
treat it as a
+                                * regular expression. Pre-compile it.
+                                */
+                               if (!token_regcomp(&parsedline->hostname_re,
+                                                                  
token->string + 1, HbaFileName,
+                                                                  line_num, 
err_msg, elevel))
+                                       return NULL;
+                               parsedline->hostname = str;
+                       }
                }
        }                                                       /* != ctLocal */
 
@@ -2135,7 +2237,8 @@ check_hba(hbaPort *port)
                                        if (hba->hostname)
                                        {
                                                if (!check_hostname(port,
-                                                                               
        hba->hostname))
+                                                                               
        hba->hostname, hba->hostname_re))
+
                                                        continue;
                                        }
                                        else
@@ -2162,10 +2265,10 @@ check_hba(hbaPort *port)
 
                /* Check database and role */
                if (!check_db(port->database_name, port->user_name, roleid,
-                                         hba->databases))
+                                         hba->databases, hba->databases_re))
                        continue;
 
-               if (!check_role(port->user_name, roleid, hba->roles))
+               if (!check_role(port->user_name, roleid, hba->roles, 
hba->roles_re))
                        continue;
 
                /* Found a record that matched! */
@@ -2342,34 +2445,9 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel)
                 * When system username starts with a slash, treat it as a 
regular
                 * expression. Pre-compile it.
                 */
-               int                     r;
-               pg_wchar   *wstr;
-               int                     wlen;
-
-               wstr = palloc((strlen(parsedline->ident_user + 1) + 1) * 
sizeof(pg_wchar));
-               wlen = pg_mb2wchar_with_len(parsedline->ident_user + 1,
-                                                                       wstr, 
strlen(parsedline->ident_user + 1));
-
-               r = pg_regcomp(&parsedline->re, wstr, wlen, REG_ADVANCED, 
C_COLLATION_OID);
-               if (r)
-               {
-                       char            errstr[100];
-
-                       pg_regerror(r, &parsedline->re, errstr, sizeof(errstr));
-                       ereport(elevel,
-                                       
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
-                                        errmsg("invalid regular expression 
\"%s\": %s",
-                                                       parsedline->ident_user 
+ 1, errstr),
-                                        errcontext("line %d of configuration 
file \"%s\"",
-                                                       line_num, 
IdentFileName)));
-
-                       *err_msg = psprintf("invalid regular expression \"%s\": 
%s",
-                                                               
parsedline->ident_user + 1, errstr);
-
-                       pfree(wstr);
+               if (!token_regcomp(&parsedline->re, parsedline->ident_user + 1,
+                                                  IdentFileName, line_num, 
err_msg, elevel))
                        return NULL;
-               }
-               pfree(wstr);
        }
 
        return parsedline;
@@ -2706,3 +2784,68 @@ hba_authname(UserAuth auth_method)
 
        return UserAuthName[auth_method];
 }
+
+/*
+ * Compile the regular expression "re" and return whether it compiles
+ * successfully or not.
+ *
+ * If not, the last 4 parameters are used to add extra details while reporting
+ * the error.
+ */
+static bool
+token_regcomp(regex_t *re, char *string, char *filename, int line_num,
+                         char **err_msg, int elevel)
+{
+       int                     r;
+       pg_wchar   *wstr;
+       int                     wlen;
+
+       wstr = palloc((strlen(string) + 1) * sizeof(pg_wchar));
+       wlen = pg_mb2wchar_with_len(string,
+                                                               wstr, 
strlen(string));
+
+       r = pg_regcomp(re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
+       if (r)
+       {
+               char            errstr[100];
+
+               pg_regerror(r, re, errstr, sizeof(errstr));
+               ereport(elevel,
+                               (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+                                errmsg("invalid regular expression \"%s\": %s",
+                                               string, errstr),
+                                errcontext("line %d of configuration file 
\"%s\"",
+                                                       line_num, filename)));
+
+               *err_msg = psprintf("invalid regular expression \"%s\": %s",
+                                                       string, errstr);
+
+               pfree(wstr);
+               return false;
+       }
+
+       pfree(wstr);
+       return true;
+}
+
+/*
+ * Return whether "match" is matching the regular expression "re" or not.
+ */
+static bool
+token_regexec(const char *match, regex_t *re)
+{
+       pg_wchar   *wmatchstr;
+       int                     wmatchlen;
+
+       wmatchstr = palloc((strlen(match) + 1) * sizeof(pg_wchar));
+       wmatchlen = pg_mb2wchar_with_len(match, wmatchstr, strlen(match));
+
+       if (pg_regexec(re, wmatchstr, wmatchlen, 0, NULL, 0, NULL, 0) == 
REG_OKAY)
+       {
+               pfree(wmatchstr);
+               return true;
+       }
+
+       pfree(wmatchstr);
+       return false;
+}
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index d06da81806..24fe502d06 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -120,6 +120,9 @@ typedef struct HbaLine
        char       *radiusidentifiers_s;
        List       *radiusports;
        char       *radiusports_s;
+       List       *roles_re;
+       List       *databases_re;
+       regex_t    hostname_re;
 } HbaLine;
 
 typedef struct IdentLine
diff --git a/src/test/authentication/t/001_password.pl 
b/src/test/authentication/t/001_password.pl
index 3e3079c824..210583c406 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -23,29 +23,32 @@ if (!$use_unix_sockets)
 # and then execute a reload to refresh it.
 sub reset_pg_hba
 {
-       my $node       = shift;
-       my $hba_method = shift;
+       my $node                = shift;
+       my $host                = shift;
+       my $database    = shift;
+       my $role                = shift;
+       my $method              = shift;
 
        unlink($node->data_dir . '/pg_hba.conf');
        # just for testing purposes, use a continuation line
-       $node->append_conf('pg_hba.conf', "local all all\\\n $hba_method");
+       $node->append_conf('pg_hba.conf', "$host $database $role\\\n $method");
        $node->reload;
        return;
 }
 
 # Test access for a single role, useful to wrap all tests into one.  Extra
 # named parameters are passed to connect_ok/fails as-is.
-sub test_role
+sub test_conn
 {
        local $Test::Builder::Level = $Test::Builder::Level + 1;
 
-       my ($node, $role, $method, $expected_res, %params) = @_;
+       my ($node, $conn, $method, $expected_res, %params) = @_;
        my $status_string = 'failed';
        $status_string = 'success' if ($expected_res eq 0);
 
-       my $connstr = "user=$role";
+       my $connstr = "$conn";
        my $testname =
-         "authentication $status_string for method $method, role $role";
+         "authentication $status_string for method $method, conn $conn";
 
        if ($expected_res eq 0)
        {
@@ -61,7 +64,11 @@ sub test_role
 # Initialize primary node
 my $node = PostgreSQL::Test::Cluster->new('primary');
 $node->init;
-$node->append_conf('postgresql.conf', "log_connections = on\n");
+$node->append_conf(
+    'postgresql.conf', qq{
+listen_addresses = '127.0.0.1'
+log_connections = on
+});
 $node->start;
 
 # Create 3 roles with different password methods for each one. The same
@@ -74,61 +81,66 @@ $node->safe_psql('postgres',
 );
 $ENV{"PGPASSWORD"} = 'pass';
 
+# Create a database to test regular expression
+$node->safe_psql('postgres',
+       "CREATE database testdb;"
+);
+
 # For "trust" method, all users should be able to connect. These users are not
 # considered to be authenticated.
-reset_pg_hba($node, 'trust');
-test_role($node, 'scram_role', 'trust', 0,
+reset_pg_hba($node, 'local','all', 'all', 'trust');
+test_conn($node, 'user=scram_role', 'trust', 0,
        log_unlike => [qr/connection authenticated:/]);
-test_role($node, 'md5_role', 'trust', 0,
+test_conn($node, 'user=md5_role', 'trust', 0,
        log_unlike => [qr/connection authenticated:/]);
 
 # For plain "password" method, all users should also be able to connect.
-reset_pg_hba($node, 'password');
-test_role($node, 'scram_role', 'password', 0,
+reset_pg_hba($node, 'local', 'all', 'all', 'password');
+test_conn($node, 'user=scram_role', 'password', 0,
        log_like =>
          [qr/connection authenticated: identity="scram_role" 
method=password/]);
-test_role($node, 'md5_role', 'password', 0,
+test_conn($node, 'user=md5_role', 'password', 0,
        log_like =>
          [qr/connection authenticated: identity="md5_role" method=password/]);
 
 # For "scram-sha-256" method, user "scram_role" should be able to connect.
-reset_pg_hba($node, 'scram-sha-256');
-test_role(
+reset_pg_hba($node, 'local', 'all', 'all', 'scram-sha-256');
+test_conn(
        $node,
-       'scram_role',
+       'user=scram_role',
        'scram-sha-256',
        0,
        log_like => [
                qr/connection authenticated: identity="scram_role" 
method=scram-sha-256/
        ]);
-test_role($node, 'md5_role', 'scram-sha-256', 2,
+test_conn($node, 'user=md5_role', 'scram-sha-256', 2,
        log_unlike => [qr/connection authenticated:/]);
 
 # Test that bad passwords are rejected.
 $ENV{"PGPASSWORD"} = 'badpass';
-test_role($node, 'scram_role', 'scram-sha-256', 2,
+test_conn($node, 'user=scram_role', 'scram-sha-256', 2,
        log_unlike => [qr/connection authenticated:/]);
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "md5" method, all users should be able to connect (SCRAM
 # authentication will be performed for the user with a SCRAM secret.)
-reset_pg_hba($node, 'md5');
-test_role($node, 'scram_role', 'md5', 0,
+reset_pg_hba($node, 'local', 'all', 'all', 'md5');
+test_conn($node, 'user=scram_role', 'md5', 0,
        log_like =>
          [qr/connection authenticated: identity="scram_role" method=md5/]);
-test_role($node, 'md5_role', 'md5', 0,
+test_conn($node, 'user=md5_role', 'md5', 0,
        log_like =>
          [qr/connection authenticated: identity="md5_role" method=md5/]);
 
 # Tests for channel binding without SSL.
 # Using the password authentication method; channel binding can't work
-reset_pg_hba($node, 'password');
+reset_pg_hba($node, 'local', 'all', 'all', 'password');
 $ENV{"PGCHANNELBINDING"} = 'require';
-test_role($node, 'scram_role', 'scram-sha-256', 2);
+test_conn($node, 'user=scram_role', 'scram-sha-256', 2);
 # SSL not in use; channel binding still can't work
-reset_pg_hba($node, 'scram-sha-256');
+reset_pg_hba($node, 'local', 'all', 'all', 'scram-sha-256');
 $ENV{"PGCHANNELBINDING"} = 'require';
-test_role($node, 'scram_role', 'scram-sha-256', 2);
+test_conn($node, 'user=scram_role', 'scram-sha-256', 2);
 
 # Test .pgpass processing; but use a temp file, don't overwrite the real one!
 my $pgpassfile = "${PostgreSQL::Test::Utils::tmp_check}/pgpass";
@@ -145,15 +157,45 @@ append_to_file(
 !);
 chmod 0600, $pgpassfile or die;
 
-reset_pg_hba($node, 'password');
-test_role($node, 'scram_role', 'password from pgpass', 0);
-test_role($node, 'md5_role',   'password from pgpass', 2);
+reset_pg_hba($node, 'local', 'all', 'all', 'password');
+test_conn($node, 'user=scram_role', 'password from pgpass', 0);
+test_conn($node, 'user=md5_role',   'password from pgpass', 2);
 
 append_to_file(
        $pgpassfile, qq!
 *:*:*:md5_role:p\\ass
 !);
 
-test_role($node, 'md5_role', 'password from pgpass', 0);
+test_conn($node, 'user=md5_role', 'password from pgpass', 0);
+
+# Testing with regular expression for username
+reset_pg_hba($node, 'local', 'all', '/^.*nomatch.*$, baduser, /^.*md.*$', 
'password');
+test_conn($node, 'user=md5_role', 'password, matching regexp for username', 0);
+
+reset_pg_hba($node, 'local', 'all', '/^.*nomatch.*$, baduser, /^.*m_d.*$', 
'password');
+test_conn($node, 'user=md5_role', 'password, non matching regexp for 
username', 2,
+               log_unlike => [qr/connection authenticated:/]);
+
+# Testing with regular expression for dbname
+reset_pg_hba($node, 'local', '/^.*nomatch.*$, baddb, /^t.*b$', 'all', 
'password');
+test_conn($node, 'user=md5_role dbname=testdb', 'password, matching regexp for 
dbname', 0);
 
+reset_pg_hba($node, 'local', '/^.*nomatch.*$, baddb, /^t.*ba$', 'all', 
'password');
+test_conn($node, 'user=md5_role dbname=testdb', 'password, non matching regexp 
for dbname', 2,
+               log_unlike => [qr/connection authenticated:/]);
+
+# Testing with regular expression for hostname
+SKIP:
+{
+       # Being able to do a reverse lookup of a hostname on Windows for 
localhost
+       # is not guaranteed on all environments by default.
+       # So, skip the regular expression test for hostname on Windows.
+       skip "Regular expression for hostname not tested on Windows", 2 if 
($windows_os);
+
+       reset_pg_hba($node, 'host', 'all', 'all  /^.*$', 'password');
+       test_conn($node, 'user=md5_role host=localhost', 'password, matching 
regexp for hostname', 0);
+
+       reset_pg_hba($node, 'host', 'all', 'all  /^$', 'password');
+       test_conn($node, 'user=md5_role host=localhost', 'password, non 
matching regexp for hostname', 2);
+}
 done_testing();

Reply via email to