static MemoryContext parsed_hba_context = NULL;
/*
- * These variables hold the pre-parsed contents of the ident usermap
- * configuration file. ident_lines is a triple-nested list of lines, fields
- * and tokens, as returned by tokenize_file. There will be one line in
- * ident_lines for each (non-empty, non-comment) line of the file. Note there
- * will always be at least one field, since blank lines are not entered in the
- * data structure. ident_line_nums is an integer list containing the actual
- * line number for each line represented in ident_lines. ident_context is
- * the memory context holding all this.
+ * pre-parsed content of ident mapping file: list of IdentLine structs.
+ * parsed_ident_context is the memory context where it lives.
+ *
+ * NOTE: the IdentLine structs can contain pre-compiled regular expressions
+ * that live outside the memory context. Before destroying or resetting the
+ * memory context, they need to be expliticly free'd.
*/
-static List *ident_lines = NIL;
-static List *ident_line_nums = NIL;
-static MemoryContext ident_context = NULL;
+static List *parsed_ident_lines = NIL;
+static MemoryContext parsed_ident_context = NULL;
static MemoryContext tokenize_file(const char *filename, FILE *file,
(errcode(ERRCODE_CONFIG_FILE_ERROR), \
errmsg("missing entry in file \"%s\" at end of line %d", \
IdentFileName, line_number))); \
- *error_p = true; \
- return; \
+ return NULL; \
} \
} while (0);
errmsg("multiple values in ident field"), \
errcontext("line %d of configuration file \"%s\"", \
line_number, IdentFileName))); \
- *error_p = true; \
- return; \
+ return NULL; \
} \
} while (0);
}
/*
- * Process one line from the ident config file.
+ * Parse one tokenised line from the ident config file and store the result in
+ * an IdentLine structure, or NULL if parsing fails.
*
- * Take the line and compare it to the needed map, pg_role and ident_user.
- * *found_p and *error_p are set according to our results.
+ * The tokenised line is a nested List of fields and tokens.
+ *
+ * If ident_user is a regular expression (ie. begins with a slash), it is
+ * compiled and stored in IdentLine structure.
+ *
+ * Note: this function leaks memory when an error occurs. Caller is expected
+ * to have set a memory context that will be reset if this function returns
+ * NULL.
*/
-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)
+static IdentLine *
+parse_ident_line(List *line, int line_number)
{
ListCell *field;
List *tokens;
HbaToken *token;
- char *file_map;
- char *file_pgrole;
- char *file_ident_user;
-
- *found_p = false;
- *error_p = false;
+ IdentLine *parsedline;
Assert(line != NIL);
field = list_head(line);
+ parsedline = palloc0(sizeof(IdentLine));
+ parsedline->linenumber = line_number;
+
/* Get the map token (must exist) */
tokens = lfirst(field);
IDENT_MULTI_VALUE(tokens);
token = linitial(tokens);
- file_map = token->string;
+ parsedline->usermap = pstrdup(token->string);
/* Get the ident user token */
field = lnext(field);
tokens = lfirst(field);
IDENT_MULTI_VALUE(tokens);
token = linitial(tokens);
- file_ident_user = token->string;
+ parsedline->ident_user = pstrdup(token->string);
/* Get the PG rolename token */
field = lnext(field);
tokens = lfirst(field);
IDENT_MULTI_VALUE(tokens);
token = linitial(tokens);
- file_pgrole = token->string;
-
- if (strcmp(file_map, usermap_name) != 0)
- /* Line does not match the map name we're looking for, so just abort */
- return;
+ parsedline->pg_role = pstrdup(token->string);
- /* Match? */
- if (file_ident_user[0] == '/')
+ if (parsedline->ident_user[0] == '/')
{
/*
* When system username starts with a slash, treat it as a regular
- * expression. In this case, we process the system username as a
- * regular expression that returns exactly one match. This is replaced
- * for \1 in the database username string, if present.
+ * expression. Pre-compile it.
*/
int r;
- regex_t re;
- regmatch_t matches[2];
pg_wchar *wstr;
int wlen;
- char *ofs;
- char *regexp_pgrole;
- wstr = palloc((strlen(file_ident_user + 1) + 1) * sizeof(pg_wchar));
- wlen = pg_mb2wchar_with_len(file_ident_user + 1, wstr, strlen(file_ident_user + 1));
+ 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));
- /*
- * XXX: Major room for optimization: regexps could be compiled when
- * the file is loaded and then re-used in every connection.
- */
- r = pg_regcomp(&re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
+ r = pg_regcomp(&parsedline->re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
if (r)
{
char errstr[100];
- pg_regerror(r, &re, errstr, sizeof(errstr));
+ pg_regerror(r, &parsedline->re, errstr, sizeof(errstr));
ereport(LOG,
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
errmsg("invalid regular expression \"%s\": %s",
- file_ident_user + 1, errstr)));
+ parsedline->ident_user + 1, errstr)));
pfree(wstr);
- *error_p = true;
- return;
+ return NULL;
}
pfree(wstr);
+ }
+
+ return parsedline;
+}
+
+/*
+ * Process one line from the parsed ident config lines.
+ *
+ * Compare input parsed ident line to the needed map, pg_role and ident_user.
+ * *found_p and *error_p are set according to our results.
+ */
+static void
+check_ident_usermap(IdentLine *identLine, const char *usermap_name,
+ const char *pg_role, const char *ident_user,
+ bool case_insensitive, bool *found_p, bool *error_p)
+{
+ *found_p = false;
+ *error_p = false;
+
+ if (strcmp(identLine->usermap, usermap_name) != 0)
+ /* Line does not match the map name we're looking for, so just abort */
+ return;
+
+ /* Match? */
+ if (identLine->ident_user[0] == '/')
+ {
+ /*
+ * When system username starts with a slash, treat it as a regular
+ * expression. In this case, we process the system username as a
+ * regular expression that returns exactly one match. This is replaced
+ * for \1 in the database username string, if present.
+ */
+ int r;
+ regmatch_t matches[2];
+ pg_wchar *wstr;
+ int wlen;
+ char *ofs;
+ char *regexp_pgrole;
wstr = palloc((strlen(ident_user) + 1) * sizeof(pg_wchar));
wlen = pg_mb2wchar_with_len(ident_user, wstr, strlen(ident_user));
- r = pg_regexec(&re, wstr, wlen, 0, NULL, 2, matches, 0);
+ r = pg_regexec(&identLine->re, wstr, wlen, 0, NULL, 2, matches, 0);
if (r)
{
char errstr[100];
if (r != REG_NOMATCH)
{
/* REG_NOMATCH is not an error, everything else is */
- pg_regerror(r, &re, errstr, sizeof(errstr));
+ pg_regerror(r, &identLine->re, errstr, sizeof(errstr));
ereport(LOG,
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
errmsg("regular expression match for \"%s\" failed: %s",
- file_ident_user + 1, errstr)));
+ identLine->ident_user + 1, errstr)));
*error_p = true;
}
pfree(wstr);
- pg_regfree(&re);
return;
}
pfree(wstr);
- if ((ofs = strstr(file_pgrole, "\\1")) != NULL)
+ if ((ofs = strstr(identLine->pg_role, "\\1")) != NULL)
{
/* substitution of the first argument requested */
if (matches[1].rm_so < 0)
ereport(LOG,
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"",
- file_ident_user + 1, file_pgrole)));
- pg_regfree(&re);
+ identLine->ident_user + 1, identLine->pg_role)));
*error_p = true;
return;
}
* length: original length minus length of \1 plus length of match
* plus null terminator
*/
- regexp_pgrole = palloc0(strlen(file_pgrole) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1);
- strncpy(regexp_pgrole, file_pgrole, (ofs - file_pgrole));
+ regexp_pgrole = palloc0(strlen(identLine->pg_role) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1);
+ strncpy(regexp_pgrole, identLine->pg_role, (ofs - identLine->pg_role));
memcpy(regexp_pgrole + strlen(regexp_pgrole),
ident_user + matches[1].rm_so,
matches[1].rm_eo - matches[1].rm_so);
else
{
/* no substitution, so copy the match */
- regexp_pgrole = pstrdup(file_pgrole);
+ regexp_pgrole = pstrdup(identLine->pg_role);
}
- pg_regfree(&re);
-
/*
* now check if the username actually matched what the user is trying
* to connect as
/* Not regular expression, so make complete match */
if (case_insensitive)
{
- if (pg_strcasecmp(file_pgrole, pg_role) == 0 &&
- pg_strcasecmp(file_ident_user, ident_user) == 0)
+ if (pg_strcasecmp(identLine->pg_role, pg_role) == 0 &&
+ pg_strcasecmp(identLine->ident_user, ident_user) == 0)
*found_p = true;
}
else
{
- if (strcmp(file_pgrole, pg_role) == 0 &&
- strcmp(file_ident_user, ident_user) == 0)
+ if (strcmp(identLine->pg_role, pg_role) == 0 &&
+ strcmp(identLine->ident_user, ident_user) == 0)
*found_p = true;
}
}
}
else
{
- ListCell *line_cell,
- *num_cell;
+ ListCell *line_cell;
- forboth(line_cell, ident_lines, num_cell, ident_line_nums)
+ foreach(line_cell, parsed_ident_lines)
{
- parse_ident_usermap(lfirst(line_cell), lfirst_int(num_cell),
- usermap_name, pg_role, auth_user, case_insensitive,
+ check_ident_usermap(lfirst(line_cell), usermap_name,
+ pg_role, auth_user, case_insensitive,
&found_entry, &error);
if (found_entry || error)
break;
/*
- * Read the ident config file and populate ident_lines and ident_line_nums.
+ * Read the ident config file and create a List of IdentLine records for
+ * the contents.
*
- * Like parsed_hba_lines, ident_lines is a triple-nested List of lines, fields
- * and tokens.
+ * This works the same as load_hba(), but for the user config file.
*/
-void
+bool
load_ident(void)
{
FILE *file;
-
- if (ident_context != NULL)
- {
- MemoryContextDelete(ident_context);
- ident_context = NULL;
- }
+ List *ident_lines = NIL;
+ List *ident_line_nums = NIL;
+ ListCell *line_cell,
+ *num_cell,
+ *parsed_line_cell;
+ List *new_parsed_lines = NIL;
+ bool ok = true;
+ MemoryContext linecxt;
+ MemoryContext oldcxt;
+ MemoryContext ident_context;
+ IdentLine *newline;
file = AllocateFile(IdentFileName, "r");
if (file == NULL)
(errcode_for_file_access(),
errmsg("could not open usermap file \"%s\": %m",
IdentFileName)));
+ return false;
}
- else
+
+ linecxt = tokenize_file(IdentFileName, file, &ident_lines, &ident_line_nums);
+ FreeFile(file);
+
+ /* Now parse all the lines */
+ ident_context = AllocSetContextCreate(TopMemoryContext,
+ "ident parser context",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ oldcxt = MemoryContextSwitchTo(ident_context);
+ forboth(line_cell, ident_lines, num_cell, ident_line_nums)
{
- ident_context = tokenize_file(IdentFileName, file, &ident_lines,
- &ident_line_nums);
- FreeFile(file);
+ if ((newline = parse_ident_line(lfirst(line_cell), lfirst_int(num_cell))) == NULL)
+ {
+ /*
+ * Parse error in the file, so indicate there's a problem. Free
+ * all the memory and regular expressions of lines parsed so far.
+ */
+ foreach(parsed_line_cell, new_parsed_lines)
+ {
+ newline = (IdentLine *) lfirst(parsed_line_cell);
+ if (newline->ident_user[0] == '/')
+ pg_regfree(&newline->re);
+ }
+ MemoryContextReset(ident_context);
+ new_parsed_lines = NIL;
+ ok = false;
+
+ /*
+ * Keep parsing the rest of the file so we can report errors on
+ * more than the first row. Error has already been reported in the
+ * parsing function, so no need to log it here.
+ */
+ continue;
+ }
+
+ new_parsed_lines = lappend(new_parsed_lines, newline);
}
+
+ /* Free tokenizer memory */
+ MemoryContextDelete(linecxt);
+ MemoryContextSwitchTo(oldcxt);
+
+ if (!ok)
+ {
+ /* File contained one or more errors, so bail out */
+ foreach(parsed_line_cell, new_parsed_lines)
+ {
+ newline = (IdentLine *) lfirst(parsed_line_cell);
+ if (newline->ident_user[0] == '/')
+ pg_regfree(&newline->re);
+ }
+ MemoryContextDelete(ident_context);
+ return false;
+ }
+
+ /* Loaded new file successfully, replace the one we use */
+ if (parsed_ident_lines != NULL)
+ {
+ foreach(parsed_line_cell, parsed_ident_lines)
+ {
+ newline = (IdentLine *) lfirst(parsed_line_cell);
+ if (newline->ident_user[0] == '/')
+ pg_regfree(&newline->re);
+ }
+ MemoryContextDelete(parsed_ident_context);
+ }
+ parsed_ident_context = ident_context;
+ parsed_ident_lines = new_parsed_lines;
+
+ return true;
}