]> granicus.if.org Git - postgresql/commitdiff
Parse pg_ident.conf when it's loaded, keeping it in memory in parsed format.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Fri, 21 Sep 2012 14:41:22 +0000 (17:41 +0300)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Fri, 21 Sep 2012 14:54:39 +0000 (17:54 +0300)
Similar changes were done to pg_hba.conf earlier already, this commit makes
pg_ident.conf to behave the same as pg_hba.conf.

This has two user-visible effects. First, if pg_ident.conf contains multiple
errors, the whole file is parsed at postmaster startup time and all the
errors are immediately reported. Before this patch, the file was parsed and
the errors were reported only when someone tries to connect using an
authentication method that uses the file, and the parsing stopped on first
error. Second, if you SIGHUP to reload the config files, and the new
pg_ident.conf file contains an error, the error is logged but the old file
stays in effect.

Also, regular expressions in pg_ident.conf are now compiled only once when
the file is loaded, rather than every time the a user is authenticated. That
should speed up authentication if you have a lot of regexps in the file.

Amit Kapila

src/backend/libpq/hba.c
src/backend/postmaster/postmaster.c
src/backend/utils/init/postinit.c
src/include/libpq/hba.h

index 828f6dcc8e1653ad25f206f9d10b55006466b2e1..7502e82860d53e5a9530f0ee42d69b3585dcd15c 100644 (file)
@@ -73,18 +73,15 @@ static List *parsed_hba_lines = NIL;
 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,
@@ -782,8 +779,7 @@ check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
                                (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);
 
@@ -794,8 +790,7 @@ check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
                                 errmsg("multiple values in ident field"), \
                                 errcontext("line %d of configuration file \"%s\"", \
                                                line_number, IdentFileName))); \
-               *error_p = true; \
-               return; \
+               return NULL; \
        } \
 } while (0);
 
@@ -1794,34 +1789,37 @@ load_hba(void)
 }
 
 /*
- *     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);
@@ -1829,7 +1827,7 @@ parse_ident_usermap(List *line, int line_number, const char *usermap_name,
        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);
@@ -1837,57 +1835,80 @@ parse_ident_usermap(List *line, int line_number, const char *usermap_name,
        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];
@@ -1895,21 +1916,20 @@ parse_ident_usermap(List *line, int line_number, const char *usermap_name,
                        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)
@@ -1917,8 +1937,7 @@ parse_ident_usermap(List *line, int line_number, const char *usermap_name,
                                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;
                        }
@@ -1927,8 +1946,8 @@ parse_ident_usermap(List *line, int line_number, const char *usermap_name,
                         * 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);
@@ -1937,11 +1956,9 @@ parse_ident_usermap(List *line, int line_number, const char *usermap_name,
                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
@@ -1965,14 +1982,14 @@ parse_ident_usermap(List *line, int line_number, const char *usermap_name,
                /* 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;
                }
        }
@@ -2021,13 +2038,12 @@ check_usermap(const char *usermap_name,
        }
        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;
@@ -2044,21 +2060,26 @@ check_usermap(const char *usermap_name,
 
 
 /*
- * 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)
@@ -2068,13 +2089,80 @@ load_ident(void)
                                (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;
 }
 
 
index 73520a6ca2f4d3300f3d8939c0e9412064a911c3..dff4c710967323fdaddc8070e8f8f01c4e3d998d 100644 (file)
@@ -1151,7 +1151,16 @@ PostmasterMain(int argc, char *argv[])
                ereport(FATAL,
                                (errmsg("could not load pg_hba.conf")));
        }
-       load_ident();
+       if (!load_ident())
+       {
+               /*
+                * We can start up without the IDENT file, although it means that you
+                * cannot log in using any of the authentication methods that need a
+                * user name mapping. load_ident() already logged the details of
+                * error to the log.
+                */
+       }
+
 
        /*
         * Remove old temporary files.  At this point there can be no other
@@ -2153,7 +2162,9 @@ SIGHUP_handler(SIGNAL_ARGS)
                        ereport(WARNING,
                                        (errmsg("pg_hba.conf not reloaded")));
 
-               load_ident();
+               if (!load_ident())
+                       ereport(WARNING,
+                                       (errmsg("pg_ident.conf not reloaded")));
 
 #ifdef EXEC_BACKEND
                /* Update the starting-point file for future children */
index abc46d6317ec63326e03a1dda670a7633db6c6df..2eb456df45e1a5a9f55689d935e6bec320cb33bd 100644 (file)
@@ -197,7 +197,16 @@ PerformAuthentication(Port *port)
                ereport(FATAL,
                                (errmsg("could not load pg_hba.conf")));
        }
-       load_ident();
+
+       if (!load_ident())
+       {
+               /*
+                * It is ok to continue if we fail to load the IDENT file, although it
+                * means that you cannot log in using any of the authentication methods
+                * that need a user name mapping. load_ident() already logged the
+                * details of error to the log.
+                */
+       }
 #endif
 
        /*
index f3b8be6a0ccd27ef3bf9427a3c3ef3d9e5ae200c..408d26263a9544ac82c14f4f8b7448b4457c9a2e 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "libpq/pqcomm.h"      /* pgrminclude ignore */        /* needed for NetBSD */
 #include "nodes/pg_list.h"
+#include "regex/regex.h"
 
 
 typedef enum UserAuth
@@ -82,11 +83,21 @@ typedef struct HbaLine
        int                     radiusport;
 } HbaLine;
 
+typedef struct IdentLine
+{
+       int             linenumber;
+
+       char       *usermap;
+       char       *ident_user;
+       char       *pg_role;
+       regex_t         re;
+} IdentLine;
+
 /* kluge to avoid including libpq/libpq-be.h here */
 typedef struct Port hbaPort;
 
 extern bool load_hba(void);
-extern void load_ident(void);
+extern bool load_ident(void);
 extern void hba_getauthmethod(hbaPort *port);
 extern int check_usermap(const char *usermap_name,
                          const char *pg_role, const char *auth_user,