Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit fc579e1

Browse files
committed
Refactor regular expression handling in hba.c
AuthToken gains a regular expression, and IdentLine is changed so as it uses an AuthToken rather than tracking separately the ident user string used for the regex compilation and its generated regex_t. In the case of pg_ident.conf, a set of AuthTokens is built in the pre-parsing phase of the file, and an extra regular expression is compiled when building the list of IdentLines, after checking the sanity of the fields in a pre-parsed entry. The logic in charge of computing and executing regular expressions is now done in a new set of routines called respectively regcomp_auth_token() and regexec_auth_token() that are wrappers around pg_regcomp() and pg_regexec(), working on AuthTokens. While on it, this patch adds a routine able to free an AuthToken, free_auth_token(), to simplify a bit the logic around the requirement of using a specific free routine for computed regular expressions. Note that there are no functional or behavior changes introduced by this commit. The goal of this patch is to ease the use of regular expressions with more items of pg_hba.conf (user list, database list, potentially hostnames) where AuthTokens are used extensively. This will be tackled later in a separate patch. Author: Bertrand Drouvot, Michael Paquier Discussion: https://postgr.es/m/fff0d7c1-8ad4-76a1-9db3-0ab6ec338bf7@amazon.com
1 parent 8bf66de commit fc579e1

File tree

3 files changed

+119
-71
lines changed

3 files changed

+119
-71
lines changed

src/backend/libpq/hba.c

+103-57
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ typedef struct check_network_data
6666
} check_network_data;
6767

6868

69+
#define token_has_regexp(t) (t->regex != NULL)
6970
#define token_is_keyword(t, k) (!t->quoted && strcmp(t->string, k) == 0)
7071
#define token_matches(t, k) (strcmp(t->string, k) == 0)
7172

@@ -80,9 +81,10 @@ static MemoryContext parsed_hba_context = NULL;
8081
* pre-parsed content of ident mapping file: list of IdentLine structs.
8182
* parsed_ident_context is the memory context where it lives.
8283
*
83-
* NOTE: the IdentLine structs can contain pre-compiled regular expressions
84-
* that live outside the memory context. Before destroying or resetting the
85-
* memory context, they need to be explicitly free'd.
84+
* NOTE: the IdentLine structs can contain AuthTokens with pre-compiled
85+
* regular expressions that live outside the memory context. Before
86+
* destroying or resetting the memory context, they need to be explicitly
87+
* free'd.
8688
*/
8789
static List *parsed_ident_lines = NIL;
8890
static MemoryContext parsed_ident_context = NULL;
@@ -117,6 +119,9 @@ static List *tokenize_inc_file(List *tokens, const char *outer_filename,
117119
const char *inc_filename, int elevel, char **err_msg);
118120
static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
119121
int elevel, char **err_msg);
122+
static int regcomp_auth_token(AuthToken *token);
123+
static int regexec_auth_token(const char *match, AuthToken *token,
124+
size_t nmatch, regmatch_t pmatch[]);
120125

121126

122127
/*
@@ -267,14 +272,26 @@ make_auth_token(const char *token, bool quoted)
267272

268273
toklen = strlen(token);
269274
/* we copy string into same palloc block as the struct */
270-
authtoken = (AuthToken *) palloc(sizeof(AuthToken) + toklen + 1);
275+
authtoken = (AuthToken *) palloc0(sizeof(AuthToken) + toklen + 1);
271276
authtoken->string = (char *) authtoken + sizeof(AuthToken);
272277
authtoken->quoted = quoted;
278+
authtoken->regex = NULL;
273279
memcpy(authtoken->string, token, toklen + 1);
274280

275281
return authtoken;
276282
}
277283

284+
/*
285+
* Free an AuthToken, that may include a regular expression that needs
286+
* to be cleaned up explicitly.
287+
*/
288+
static void
289+
free_auth_token(AuthToken *token)
290+
{
291+
if (token_has_regexp(token))
292+
pg_regfree(token->regex);
293+
}
294+
278295
/*
279296
* Copy a AuthToken struct into freshly palloc'd memory.
280297
*/
@@ -286,6 +303,56 @@ copy_auth_token(AuthToken *in)
286303
return out;
287304
}
288305

306+
/*
307+
* Compile the regular expression and store it in the AuthToken given in
308+
* input. Returns the result of pg_regcomp().
309+
*/
310+
static int
311+
regcomp_auth_token(AuthToken *token)
312+
{
313+
pg_wchar *wstr;
314+
int wlen;
315+
int rc;
316+
317+
Assert(token->regex == NULL);
318+
319+
if (token->string[0] != '/')
320+
return 0; /* nothing to compile */
321+
322+
token->regex = (regex_t *) palloc0(sizeof(regex_t));
323+
wstr = palloc((strlen(token->string + 1) + 1) * sizeof(pg_wchar));
324+
wlen = pg_mb2wchar_with_len(token->string + 1,
325+
wstr, strlen(token->string + 1));
326+
327+
rc = pg_regcomp(token->regex, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
328+
329+
pfree(wstr);
330+
return rc;
331+
}
332+
333+
/*
334+
* Execute a regular expression computed in an AuthToken, checking for a match
335+
* with the string specified in "match". The caller may optionally give an
336+
* array to store the matches. Returns the result of pg_regexec().
337+
*/
338+
static int
339+
regexec_auth_token(const char *match, AuthToken *token, size_t nmatch,
340+
regmatch_t pmatch[])
341+
{
342+
pg_wchar *wmatchstr;
343+
int wmatchlen;
344+
int r;
345+
346+
Assert(token->string[0] == '/' && token->regex);
347+
348+
wmatchstr = palloc((strlen(match) + 1) * sizeof(pg_wchar));
349+
wmatchlen = pg_mb2wchar_with_len(match, wmatchstr, strlen(match));
350+
351+
r = pg_regexec(token->regex, wmatchstr, wmatchlen, 0, NULL, nmatch, pmatch, 0);
352+
353+
pfree(wmatchstr);
354+
return r;
355+
}
289356

290357
/*
291358
* Tokenize one HBA field from a line, handling file inclusion and comma lists.
@@ -2307,6 +2374,7 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel)
23072374
List *tokens;
23082375
AuthToken *token;
23092376
IdentLine *parsedline;
2377+
int rc;
23102378

23112379
Assert(tok_line->fields != NIL);
23122380
field = list_head(tok_line->fields);
@@ -2326,7 +2394,9 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel)
23262394
tokens = lfirst(field);
23272395
IDENT_MULTI_VALUE(tokens);
23282396
token = linitial(tokens);
2329-
parsedline->ident_user = pstrdup(token->string);
2397+
2398+
/* Copy the ident user token */
2399+
parsedline->token = copy_auth_token(token);
23302400

23312401
/* Get the PG rolename token */
23322402
field = lnext(tok_line->fields, field);
@@ -2336,40 +2406,27 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel)
23362406
token = linitial(tokens);
23372407
parsedline->pg_role = pstrdup(token->string);
23382408

2339-
if (parsedline->ident_user[0] == '/')
2409+
/*
2410+
* Now that the field validation is done, compile a regex from the user
2411+
* token, if necessary.
2412+
*/
2413+
rc = regcomp_auth_token(parsedline->token);
2414+
if (rc)
23402415
{
2341-
/*
2342-
* When system username starts with a slash, treat it as a regular
2343-
* expression. Pre-compile it.
2344-
*/
2345-
int r;
2346-
pg_wchar *wstr;
2347-
int wlen;
2348-
2349-
wstr = palloc((strlen(parsedline->ident_user + 1) + 1) * sizeof(pg_wchar));
2350-
wlen = pg_mb2wchar_with_len(parsedline->ident_user + 1,
2351-
wstr, strlen(parsedline->ident_user + 1));
2352-
2353-
r = pg_regcomp(&parsedline->re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
2354-
if (r)
2355-
{
2356-
char errstr[100];
2416+
char errstr[100];
23572417

2358-
pg_regerror(r, &parsedline->re, errstr, sizeof(errstr));
2359-
ereport(elevel,
2360-
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
2361-
errmsg("invalid regular expression \"%s\": %s",
2362-
parsedline->ident_user + 1, errstr),
2363-
errcontext("line %d of configuration file \"%s\"",
2418+
pg_regerror(rc, parsedline->token->regex, errstr, sizeof(errstr));
2419+
ereport(elevel,
2420+
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
2421+
errmsg("invalid regular expression \"%s\": %s",
2422+
parsedline->token->string + 1, errstr),
2423+
errcontext("line %d of configuration file \"%s\"",
23642424
line_num, IdentFileName)));
23652425

2366-
*err_msg = psprintf("invalid regular expression \"%s\": %s",
2367-
parsedline->ident_user + 1, errstr);
2426+
*err_msg = psprintf("invalid regular expression \"%s\": %s",
2427+
parsedline->token->string + 1, errstr);
23682428

2369-
pfree(wstr);
2370-
return NULL;
2371-
}
2372-
pfree(wstr);
2429+
return NULL;
23732430
}
23742431

23752432
return parsedline;
@@ -2394,44 +2451,35 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name,
23942451
return;
23952452

23962453
/* Match? */
2397-
if (identLine->ident_user[0] == '/')
2454+
if (token_has_regexp(identLine->token))
23982455
{
23992456
/*
2400-
* When system username starts with a slash, treat it as a regular
2401-
* expression. In this case, we process the system username as a
2402-
* regular expression that returns exactly one match. This is replaced
2403-
* for \1 in the database username string, if present.
2457+
* Process the system username as a regular expression that returns
2458+
* exactly one match. This is replaced for \1 in the database username
2459+
* string, if present.
24042460
*/
24052461
int r;
24062462
regmatch_t matches[2];
2407-
pg_wchar *wstr;
2408-
int wlen;
24092463
char *ofs;
24102464
char *regexp_pgrole;
24112465

2412-
wstr = palloc((strlen(ident_user) + 1) * sizeof(pg_wchar));
2413-
wlen = pg_mb2wchar_with_len(ident_user, wstr, strlen(ident_user));
2414-
2415-
r = pg_regexec(&identLine->re, wstr, wlen, 0, NULL, 2, matches, 0);
2466+
r = regexec_auth_token(ident_user, identLine->token, 2, matches);
24162467
if (r)
24172468
{
24182469
char errstr[100];
24192470

24202471
if (r != REG_NOMATCH)
24212472
{
24222473
/* REG_NOMATCH is not an error, everything else is */
2423-
pg_regerror(r, &identLine->re, errstr, sizeof(errstr));
2474+
pg_regerror(r, identLine->token->regex, errstr, sizeof(errstr));
24242475
ereport(LOG,
24252476
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
24262477
errmsg("regular expression match for \"%s\" failed: %s",
2427-
identLine->ident_user + 1, errstr)));
2478+
identLine->token->string + 1, errstr)));
24282479
*error_p = true;
24292480
}
2430-
2431-
pfree(wstr);
24322481
return;
24332482
}
2434-
pfree(wstr);
24352483

24362484
if ((ofs = strstr(identLine->pg_role, "\\1")) != NULL)
24372485
{
@@ -2443,7 +2491,7 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name,
24432491
ereport(LOG,
24442492
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
24452493
errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"",
2446-
identLine->ident_user + 1, identLine->pg_role)));
2494+
identLine->token->string + 1, identLine->pg_role)));
24472495
*error_p = true;
24482496
return;
24492497
}
@@ -2490,13 +2538,13 @@ check_ident_usermap(IdentLine *identLine, const char *usermap_name,
24902538
if (case_insensitive)
24912539
{
24922540
if (pg_strcasecmp(identLine->pg_role, pg_role) == 0 &&
2493-
pg_strcasecmp(identLine->ident_user, ident_user) == 0)
2541+
pg_strcasecmp(identLine->token->string, ident_user) == 0)
24942542
*found_p = true;
24952543
}
24962544
else
24972545
{
24982546
if (strcmp(identLine->pg_role, pg_role) == 0 &&
2499-
strcmp(identLine->ident_user, ident_user) == 0)
2547+
strcmp(identLine->token->string, ident_user) == 0)
25002548
*found_p = true;
25012549
}
25022550
}
@@ -2646,8 +2694,7 @@ load_ident(void)
26462694
foreach(parsed_line_cell, new_parsed_lines)
26472695
{
26482696
newline = (IdentLine *) lfirst(parsed_line_cell);
2649-
if (newline->ident_user[0] == '/')
2650-
pg_regfree(&newline->re);
2697+
free_auth_token(newline->token);
26512698
}
26522699
MemoryContextDelete(ident_context);
26532700
return false;
@@ -2659,8 +2706,7 @@ load_ident(void)
26592706
foreach(parsed_line_cell, parsed_ident_lines)
26602707
{
26612708
newline = (IdentLine *) lfirst(parsed_line_cell);
2662-
if (newline->ident_user[0] == '/')
2663-
pg_regfree(&newline->re);
2709+
free_auth_token(newline->token);
26642710
}
26652711
}
26662712
if (parsed_ident_context != NULL)

src/backend/utils/adt/hbafuncs.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
467467
if (ident != NULL)
468468
{
469469
values[index++] = CStringGetTextDatum(ident->usermap);
470-
values[index++] = CStringGetTextDatum(ident->ident_user);
470+
values[index++] = CStringGetTextDatum(ident->token->string);
471471
values[index++] = CStringGetTextDatum(ident->pg_role);
472472
}
473473
else

src/include/libpq/hba.h

+15-13
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,20 @@ typedef enum ClientCertName
7777
clientCertDN
7878
} ClientCertName;
7979

80+
/*
81+
* A single string token lexed from an authentication configuration file
82+
* (pg_ident.conf or pg_hba.conf), together with whether the token has
83+
* been quoted. If "string" begins with a slash, it may optionally
84+
* contain a regular expression (currently used for pg_ident.conf when
85+
* building IdentLines).
86+
*/
87+
typedef struct AuthToken
88+
{
89+
char *string;
90+
bool quoted;
91+
regex_t *regex;
92+
} AuthToken;
93+
8094
typedef struct HbaLine
8195
{
8296
int linenumber;
@@ -127,22 +141,10 @@ typedef struct IdentLine
127141
int linenumber;
128142

129143
char *usermap;
130-
char *ident_user;
131144
char *pg_role;
132-
regex_t re;
145+
AuthToken *token;
133146
} IdentLine;
134147

135-
/*
136-
* A single string token lexed from an authentication configuration file
137-
* (pg_ident.conf or pg_hba.conf), together with whether the token has
138-
* been quoted.
139-
*/
140-
typedef struct AuthToken
141-
{
142-
char *string;
143-
bool quoted;
144-
} AuthToken;
145-
146148
/*
147149
* TokenizedAuthLine represents one line lexed from an authentication
148150
* configuration file. Each item in the "fields" list is a sub-list of

0 commit comments

Comments
 (0)