Hi, The cfbot says that the patch doesn't apply anymore, so here's a v3 with the changes mentioned below.
On Tue, Mar 01, 2022 at 05:19:50PM +0800, Julien Rouhaud wrote: > > If you prefer to interleave static and non static function I can change it. Change the split to not reorder functions. > > +#include "utils/guc.h" > > +//#include "utils/tuplestore.h" > > Yes I noticed this one this morning. I didn't want to send a new patch > version > just for that, but I already fixed it locally. Included. > Yes I'm aware of that thread. I will be happy to change the patch to use > MakeFuncResultTuplestore() as soon as it lands. Thanks for the notice though. Done, with the new SetSingleFuncCall(). > > It could be possible to do installcheck on an instance that has user > > mappings meaning that this had better be ">= 0", no? > > I thought about it, and supposed it would bring a bit more value with the test > like that. I can change it if you prefer. Change this way.
>From 865b181ea989bb5c52fad2c3c9e20a38aa173cf3 Mon Sep 17 00:00:00 2001 From: Julien Rouhaud <julien.rouh...@free.fr> Date: Tue, 1 Mar 2022 21:45:42 +0800 Subject: [PATCH v3 1/4] Extract view processing code from hba.c This file is already quite big and a following commit will add yet an additional view, so let's move all the view related code in hba.c into a new adt/hbafuncs.c. Author: Julien Rouhaud Reviewed-by: FIXME Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya%40jrouhaud --- src/backend/libpq/hba.c | 462 ++----------------------------- src/backend/utils/adt/Makefile | 1 + src/backend/utils/adt/hbafuncs.c | 423 ++++++++++++++++++++++++++++ src/include/libpq/hba.h | 29 ++ src/tools/pgindent/typedefs.list | 2 +- 5 files changed, 475 insertions(+), 442 deletions(-) create mode 100644 src/backend/utils/adt/hbafuncs.c diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 90953c38f3..f9843a0b30 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -68,32 +68,6 @@ typedef struct check_network_data #define token_is_keyword(t, k) (!t->quoted && strcmp(t->string, k) == 0) #define token_matches(t, k) (strcmp(t->string, k) == 0) -/* - * A single string token lexed from a config file, together with whether - * the token had been quoted. - */ -typedef struct HbaToken -{ - char *string; - bool quoted; -} HbaToken; - -/* - * TokenizedLine represents one line lexed from a config file. - * Each item in the "fields" list is a sub-list of HbaTokens. - * We don't emit a TokenizedLine for empty or all-comment lines, - * so "fields" is never NIL (nor are any of its sub-lists). - * Exception: if an error occurs during tokenization, we might - * have fields == NIL, in which case err_msg != NULL. - */ -typedef struct TokenizedLine -{ - List *fields; /* List of lists of HbaTokens */ - int line_num; /* Line number */ - char *raw_line; /* Raw line text */ - char *err_msg; /* Error message if any */ -} TokenizedLine; - /* * pre-parsed content of HBA config file: list of HbaLine structs. * parsed_hba_context is the memory context where it lives. @@ -138,16 +112,10 @@ static const char *const UserAuthName[] = }; -static MemoryContext tokenize_file(const char *filename, FILE *file, - List **tok_lines, int elevel); 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 ArrayType *gethba_options(HbaLine *hba); -static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, - int lineno, HbaLine *hba, const char *err_msg); -static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc); /* @@ -419,7 +387,7 @@ tokenize_inc_file(List *tokens, } /* There is possible recursion here if the file contains @ */ - linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines, elevel); + linecxt = tokenize_auth_file(inc_fullname, inc_file, &inc_lines, elevel); FreeFile(inc_file); pfree(inc_fullname); @@ -427,7 +395,7 @@ tokenize_inc_file(List *tokens, /* Copy all tokens found in the file and append to the tokens list */ foreach(inc_line, inc_lines) { - TokenizedLine *tok_line = (TokenizedLine *) lfirst(inc_line); + TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(inc_line); ListCell *inc_field; /* If any line has an error, propagate that up to caller */ @@ -458,7 +426,8 @@ tokenize_inc_file(List *tokens, /* * Tokenize the given file. * - * The output is a list of TokenizedLine structs; see struct definition above. + * The output is a list of TokenizedAuthLine structs; see struct definition + * above. * * filename: the absolute path to the target file * file: the already-opened target file @@ -466,14 +435,15 @@ tokenize_inc_file(List *tokens, * elevel: message logging level * * Errors are reported by logging messages at ereport level elevel and by - * adding TokenizedLine structs containing non-null err_msg fields to the + * adding TokenizedAuthLine structs containing non-null err_msg fields to the * output list. * * Return value is a memory context which contains all memory allocated by * this function (it's a child of caller's context). */ -static MemoryContext -tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel) +MemoryContext +tokenize_auth_file(const char *filename, FILE *file, List **tok_lines, + int elevel) { int line_number = 1; StringInfoData buf; @@ -481,7 +451,7 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel) MemoryContext oldcxt; linecxt = AllocSetContextCreate(CurrentMemoryContext, - "tokenize_file", + "tokenize_auth_file", ALLOCSET_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(linecxt); @@ -550,12 +520,14 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel) current_line = lappend(current_line, current_field); } - /* Reached EOL; emit line to TokenizedLine list unless it's boring */ + /* + * Reached EOL; emit line to TokenizedAuthLine list unless it's boring + */ if (current_line != NIL || err_msg != NULL) { - TokenizedLine *tok_line; + TokenizedAuthLine *tok_line; - tok_line = (TokenizedLine *) palloc(sizeof(TokenizedLine)); + tok_line = (TokenizedAuthLine *) palloc(sizeof(TokenizedAuthLine)); tok_line->fields = current_line; tok_line->line_num = line_number; tok_line->raw_line = pstrdup(buf.data); @@ -962,8 +934,8 @@ do { \ * to have set a memory context that will be reset if this function returns * NULL. */ -static HbaLine * -parse_hba_line(TokenizedLine *tok_line, int elevel) +HbaLine * +parse_hba_line(TokenizedAuthLine *tok_line, int elevel) { int line_num = tok_line->line_num; char **err_msg = &tok_line->err_msg; @@ -2257,7 +2229,7 @@ load_hba(void) return false; } - linecxt = tokenize_file(HbaFileName, file, &hba_lines, LOG); + linecxt = tokenize_auth_file(HbaFileName, file, &hba_lines, LOG); FreeFile(file); /* Now parse all the lines */ @@ -2268,7 +2240,7 @@ load_hba(void) oldcxt = MemoryContextSwitchTo(hbacxt); foreach(line, hba_lines) { - TokenizedLine *tok_line = (TokenizedLine *) lfirst(line); + TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line); HbaLine *newline; /* don't parse lines that already have errors */ @@ -2328,398 +2300,6 @@ load_hba(void) return true; } -/* - * This macro specifies the maximum number of authentication options - * that are possible with any given authentication method that is supported. - * Currently LDAP supports 11, and there are 3 that are not dependent on - * the auth method here. It may not actually be possible to set all of them - * at the same time, but we'll set the macro value high enough to be - * conservative and avoid warnings from static analysis tools. - */ -#define MAX_HBA_OPTIONS 14 - -/* - * Create a text array listing the options specified in the HBA line. - * Return NULL if no options are specified. - */ -static ArrayType * -gethba_options(HbaLine *hba) -{ - int noptions; - Datum options[MAX_HBA_OPTIONS]; - - noptions = 0; - - if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI) - { - if (hba->include_realm) - options[noptions++] = - CStringGetTextDatum("include_realm=true"); - - if (hba->krb_realm) - options[noptions++] = - CStringGetTextDatum(psprintf("krb_realm=%s", hba->krb_realm)); - } - - if (hba->usermap) - options[noptions++] = - CStringGetTextDatum(psprintf("map=%s", hba->usermap)); - - if (hba->clientcert != clientCertOff) - options[noptions++] = - CStringGetTextDatum(psprintf("clientcert=%s", (hba->clientcert == clientCertCA) ? "verify-ca" : "verify-full")); - - if (hba->pamservice) - options[noptions++] = - CStringGetTextDatum(psprintf("pamservice=%s", hba->pamservice)); - - if (hba->auth_method == uaLDAP) - { - if (hba->ldapserver) - options[noptions++] = - CStringGetTextDatum(psprintf("ldapserver=%s", hba->ldapserver)); - - if (hba->ldapport) - options[noptions++] = - CStringGetTextDatum(psprintf("ldapport=%d", hba->ldapport)); - - if (hba->ldaptls) - options[noptions++] = - CStringGetTextDatum("ldaptls=true"); - - if (hba->ldapprefix) - options[noptions++] = - CStringGetTextDatum(psprintf("ldapprefix=%s", hba->ldapprefix)); - - if (hba->ldapsuffix) - options[noptions++] = - CStringGetTextDatum(psprintf("ldapsuffix=%s", hba->ldapsuffix)); - - if (hba->ldapbasedn) - options[noptions++] = - CStringGetTextDatum(psprintf("ldapbasedn=%s", hba->ldapbasedn)); - - if (hba->ldapbinddn) - options[noptions++] = - CStringGetTextDatum(psprintf("ldapbinddn=%s", hba->ldapbinddn)); - - if (hba->ldapbindpasswd) - options[noptions++] = - CStringGetTextDatum(psprintf("ldapbindpasswd=%s", - hba->ldapbindpasswd)); - - if (hba->ldapsearchattribute) - options[noptions++] = - CStringGetTextDatum(psprintf("ldapsearchattribute=%s", - hba->ldapsearchattribute)); - - if (hba->ldapsearchfilter) - options[noptions++] = - CStringGetTextDatum(psprintf("ldapsearchfilter=%s", - hba->ldapsearchfilter)); - - if (hba->ldapscope) - options[noptions++] = - CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope)); - } - - if (hba->auth_method == uaRADIUS) - { - if (hba->radiusservers_s) - options[noptions++] = - CStringGetTextDatum(psprintf("radiusservers=%s", hba->radiusservers_s)); - - if (hba->radiussecrets_s) - options[noptions++] = - CStringGetTextDatum(psprintf("radiussecrets=%s", hba->radiussecrets_s)); - - if (hba->radiusidentifiers_s) - options[noptions++] = - CStringGetTextDatum(psprintf("radiusidentifiers=%s", hba->radiusidentifiers_s)); - - if (hba->radiusports_s) - options[noptions++] = - CStringGetTextDatum(psprintf("radiusports=%s", hba->radiusports_s)); - } - - /* If you add more options, consider increasing MAX_HBA_OPTIONS. */ - Assert(noptions <= MAX_HBA_OPTIONS); - - if (noptions > 0) - return construct_array(options, noptions, TEXTOID, -1, false, TYPALIGN_INT); - else - return NULL; -} - -/* Number of columns in pg_hba_file_rules view */ -#define NUM_PG_HBA_FILE_RULES_ATTS 9 - -/* - * fill_hba_line: build one row of pg_hba_file_rules view, add it to tuplestore - * - * tuple_store: where to store data - * tupdesc: tuple descriptor for the view - * lineno: pg_hba.conf line number (must always be valid) - * hba: parsed line data (can be NULL, in which case err_msg should be set) - * err_msg: error message (NULL if none) - * - * Note: leaks memory, but we don't care since this is run in a short-lived - * memory context. - */ -static void -fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, - int lineno, HbaLine *hba, const char *err_msg) -{ - Datum values[NUM_PG_HBA_FILE_RULES_ATTS]; - bool nulls[NUM_PG_HBA_FILE_RULES_ATTS]; - char buffer[NI_MAXHOST]; - HeapTuple tuple; - int index; - ListCell *lc; - const char *typestr; - const char *addrstr; - const char *maskstr; - ArrayType *options; - - Assert(tupdesc->natts == NUM_PG_HBA_FILE_RULES_ATTS); - - memset(values, 0, sizeof(values)); - memset(nulls, 0, sizeof(nulls)); - index = 0; - - /* line_number */ - values[index++] = Int32GetDatum(lineno); - - if (hba != NULL) - { - /* type */ - /* Avoid a default: case so compiler will warn about missing cases */ - typestr = NULL; - switch (hba->conntype) - { - case ctLocal: - typestr = "local"; - break; - case ctHost: - typestr = "host"; - break; - case ctHostSSL: - typestr = "hostssl"; - break; - case ctHostNoSSL: - typestr = "hostnossl"; - break; - case ctHostGSS: - typestr = "hostgssenc"; - break; - case ctHostNoGSS: - typestr = "hostnogssenc"; - break; - } - if (typestr) - values[index++] = CStringGetTextDatum(typestr); - else - nulls[index++] = true; - - /* database */ - if (hba->databases) - { - /* - * Flatten HbaToken list to string list. It might seem that we - * should re-quote any quoted tokens, but that has been rejected - * on the grounds that it makes it harder to compare the array - * elements to other system catalogs. That makes entries like - * "all" or "samerole" formally ambiguous ... but users who name - * databases/roles that way are inflicting their own pain. - */ - List *names = NIL; - - foreach(lc, hba->databases) - { - HbaToken *tok = lfirst(lc); - - names = lappend(names, tok->string); - } - values[index++] = PointerGetDatum(strlist_to_textarray(names)); - } - else - nulls[index++] = true; - - /* user */ - if (hba->roles) - { - /* Flatten HbaToken list to string list; see comment above */ - List *roles = NIL; - - foreach(lc, hba->roles) - { - HbaToken *tok = lfirst(lc); - - roles = lappend(roles, tok->string); - } - values[index++] = PointerGetDatum(strlist_to_textarray(roles)); - } - else - nulls[index++] = true; - - /* address and netmask */ - /* Avoid a default: case so compiler will warn about missing cases */ - addrstr = maskstr = NULL; - switch (hba->ip_cmp_method) - { - case ipCmpMask: - if (hba->hostname) - { - addrstr = hba->hostname; - } - else - { - /* - * Note: if pg_getnameinfo_all fails, it'll set buffer to - * "???", which we want to return. - */ - if (hba->addrlen > 0) - { - if (pg_getnameinfo_all(&hba->addr, hba->addrlen, - buffer, sizeof(buffer), - NULL, 0, - NI_NUMERICHOST) == 0) - clean_ipv6_addr(hba->addr.ss_family, buffer); - addrstr = pstrdup(buffer); - } - if (hba->masklen > 0) - { - if (pg_getnameinfo_all(&hba->mask, hba->masklen, - buffer, sizeof(buffer), - NULL, 0, - NI_NUMERICHOST) == 0) - clean_ipv6_addr(hba->mask.ss_family, buffer); - maskstr = pstrdup(buffer); - } - } - break; - case ipCmpAll: - addrstr = "all"; - break; - case ipCmpSameHost: - addrstr = "samehost"; - break; - case ipCmpSameNet: - addrstr = "samenet"; - break; - } - if (addrstr) - values[index++] = CStringGetTextDatum(addrstr); - else - nulls[index++] = true; - if (maskstr) - values[index++] = CStringGetTextDatum(maskstr); - else - nulls[index++] = true; - - /* auth_method */ - values[index++] = CStringGetTextDatum(hba_authname(hba->auth_method)); - - /* options */ - options = gethba_options(hba); - if (options) - values[index++] = PointerGetDatum(options); - else - nulls[index++] = true; - } - else - { - /* no parsing result, so set relevant fields to nulls */ - memset(&nulls[1], true, (NUM_PG_HBA_FILE_RULES_ATTS - 2) * sizeof(bool)); - } - - /* error */ - if (err_msg) - values[NUM_PG_HBA_FILE_RULES_ATTS - 1] = CStringGetTextDatum(err_msg); - else - nulls[NUM_PG_HBA_FILE_RULES_ATTS - 1] = true; - - tuple = heap_form_tuple(tupdesc, values, nulls); - tuplestore_puttuple(tuple_store, tuple); -} - -/* - * Read the pg_hba.conf file and fill the tuplestore with view records. - */ -static void -fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc) -{ - FILE *file; - List *hba_lines = NIL; - ListCell *line; - MemoryContext linecxt; - MemoryContext hbacxt; - MemoryContext oldcxt; - - /* - * In the unlikely event that we can't open pg_hba.conf, we throw an - * error, rather than trying to report it via some sort of view entry. - * (Most other error conditions should result in a message in a view - * entry.) - */ - file = AllocateFile(HbaFileName, "r"); - if (file == NULL) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not open configuration file \"%s\": %m", - HbaFileName))); - - linecxt = tokenize_file(HbaFileName, file, &hba_lines, DEBUG3); - FreeFile(file); - - /* Now parse all the lines */ - hbacxt = AllocSetContextCreate(CurrentMemoryContext, - "hba parser context", - ALLOCSET_SMALL_SIZES); - oldcxt = MemoryContextSwitchTo(hbacxt); - foreach(line, hba_lines) - { - TokenizedLine *tok_line = (TokenizedLine *) lfirst(line); - HbaLine *hbaline = NULL; - - /* don't parse lines that already have errors */ - if (tok_line->err_msg == NULL) - hbaline = parse_hba_line(tok_line, DEBUG3); - - fill_hba_line(tuple_store, tupdesc, tok_line->line_num, - hbaline, tok_line->err_msg); - } - - /* Free tokenizer memory */ - MemoryContextDelete(linecxt); - /* Free parse_hba_line memory */ - MemoryContextSwitchTo(oldcxt); - MemoryContextDelete(hbacxt); -} - -/* - * SQL-accessible SRF to return all the entries in the pg_hba.conf file. - */ -Datum -pg_hba_file_rules(PG_FUNCTION_ARGS) -{ - ReturnSetInfo *rsi; - - /* - * Build tuplestore to hold the result rows. We must use the Materialize - * mode to be safe against HBA file changes while the cursor is open. - * It's also more efficient than having to look up our current position in - * the parsed list every time. - */ - SetSingleFuncCall(fcinfo, 0); - - /* Fill the tuplestore */ - rsi = (ReturnSetInfo *) fcinfo->resultinfo; - fill_hba_view(rsi->setResult, rsi->setDesc); - - PG_RETURN_NULL(); -} - /* * Parse one tokenised line from the ident config file and store the result in @@ -2735,7 +2315,7 @@ pg_hba_file_rules(PG_FUNCTION_ARGS) * NULL. */ static IdentLine * -parse_ident_line(TokenizedLine *tok_line) +parse_ident_line(TokenizedAuthLine *tok_line) { int line_num = tok_line->line_num; ListCell *field; @@ -3026,7 +2606,7 @@ load_ident(void) return false; } - linecxt = tokenize_file(IdentFileName, file, &ident_lines, LOG); + linecxt = tokenize_auth_file(IdentFileName, file, &ident_lines, LOG); FreeFile(file); /* Now parse all the lines */ @@ -3037,7 +2617,7 @@ load_ident(void) oldcxt = MemoryContextSwitchTo(ident_context); foreach(line_cell, ident_lines) { - TokenizedLine *tok_line = (TokenizedLine *) lfirst(line_cell); + TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line_cell); /* don't parse lines that already have errors */ if (tok_line->err_msg != NULL) diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 41b486bcef..7c722ea2ce 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -42,6 +42,7 @@ OBJS = \ geo_ops.o \ geo_selfuncs.o \ geo_spgist.o \ + hbafuncs.o \ inet_cidr_ntop.o \ inet_net_pton.o \ int.o \ diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c new file mode 100644 index 0000000000..f230bad8b6 --- /dev/null +++ b/src/backend/utils/adt/hbafuncs.c @@ -0,0 +1,423 @@ +/*------------------------------------------------------------------------- + * + * hbafuncs.c + * Support functions for authentication files SQL views. + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/adt/hbafuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/objectaddress.h" +#include "common/ip.h" +#include "funcapi.h" +#include "libpq/hba.h" +#include "miscadmin.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/guc.h" + + +static ArrayType *gethba_options(HbaLine *hba); +static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, + int lineno, HbaLine *hba, const char *err_msg); +static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc); + + +/* + * This macro specifies the maximum number of authentication options + * that are possible with any given authentication method that is supported. + * Currently LDAP supports 11, and there are 3 that are not dependent on + * the auth method here. It may not actually be possible to set all of them + * at the same time, but we'll set the macro value high enough to be + * conservative and avoid warnings from static analysis tools. + */ +#define MAX_HBA_OPTIONS 14 + +/* + * Create a text array listing the options specified in the HBA line. + * Return NULL if no options are specified. + */ +static ArrayType * +gethba_options(HbaLine *hba) +{ + int noptions; + Datum options[MAX_HBA_OPTIONS]; + + noptions = 0; + + if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI) + { + if (hba->include_realm) + options[noptions++] = + CStringGetTextDatum("include_realm=true"); + + if (hba->krb_realm) + options[noptions++] = + CStringGetTextDatum(psprintf("krb_realm=%s", hba->krb_realm)); + } + + if (hba->usermap) + options[noptions++] = + CStringGetTextDatum(psprintf("map=%s", hba->usermap)); + + if (hba->clientcert != clientCertOff) + options[noptions++] = + CStringGetTextDatum(psprintf("clientcert=%s", (hba->clientcert == clientCertCA) ? "verify-ca" : "verify-full")); + + if (hba->pamservice) + options[noptions++] = + CStringGetTextDatum(psprintf("pamservice=%s", hba->pamservice)); + + if (hba->auth_method == uaLDAP) + { + if (hba->ldapserver) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapserver=%s", hba->ldapserver)); + + if (hba->ldapport) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapport=%d", hba->ldapport)); + + if (hba->ldaptls) + options[noptions++] = + CStringGetTextDatum("ldaptls=true"); + + if (hba->ldapprefix) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapprefix=%s", hba->ldapprefix)); + + if (hba->ldapsuffix) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapsuffix=%s", hba->ldapsuffix)); + + if (hba->ldapbasedn) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapbasedn=%s", hba->ldapbasedn)); + + if (hba->ldapbinddn) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapbinddn=%s", hba->ldapbinddn)); + + if (hba->ldapbindpasswd) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapbindpasswd=%s", + hba->ldapbindpasswd)); + + if (hba->ldapsearchattribute) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapsearchattribute=%s", + hba->ldapsearchattribute)); + + if (hba->ldapsearchfilter) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapsearchfilter=%s", + hba->ldapsearchfilter)); + + if (hba->ldapscope) + options[noptions++] = + CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope)); + } + + if (hba->auth_method == uaRADIUS) + { + if (hba->radiusservers_s) + options[noptions++] = + CStringGetTextDatum(psprintf("radiusservers=%s", hba->radiusservers_s)); + + if (hba->radiussecrets_s) + options[noptions++] = + CStringGetTextDatum(psprintf("radiussecrets=%s", hba->radiussecrets_s)); + + if (hba->radiusidentifiers_s) + options[noptions++] = + CStringGetTextDatum(psprintf("radiusidentifiers=%s", hba->radiusidentifiers_s)); + + if (hba->radiusports_s) + options[noptions++] = + CStringGetTextDatum(psprintf("radiusports=%s", hba->radiusports_s)); + } + + /* If you add more options, consider increasing MAX_HBA_OPTIONS. */ + Assert(noptions <= MAX_HBA_OPTIONS); + + if (noptions > 0) + return construct_array(options, noptions, TEXTOID, -1, false, TYPALIGN_INT); + else + return NULL; +} + +/* Number of columns in pg_hba_file_rules view */ +#define NUM_PG_HBA_FILE_RULES_ATTS 9 + +/* + * fill_hba_line: build one row of pg_hba_file_rules view, add it to tuplestore + * + * tuple_store: where to store data + * tupdesc: tuple descriptor for the view + * lineno: pg_hba.conf line number (must always be valid) + * hba: parsed line data (can be NULL, in which case err_msg should be set) + * err_msg: error message (NULL if none) + * + * Note: leaks memory, but we don't care since this is run in a short-lived + * memory context. + */ +static void +fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, + int lineno, HbaLine *hba, const char *err_msg) +{ + Datum values[NUM_PG_HBA_FILE_RULES_ATTS]; + bool nulls[NUM_PG_HBA_FILE_RULES_ATTS]; + char buffer[NI_MAXHOST]; + HeapTuple tuple; + int index; + ListCell *lc; + const char *typestr; + const char *addrstr; + const char *maskstr; + ArrayType *options; + + Assert(tupdesc->natts == NUM_PG_HBA_FILE_RULES_ATTS); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + index = 0; + + /* line_number */ + values[index++] = Int32GetDatum(lineno); + + if (hba != NULL) + { + /* type */ + /* Avoid a default: case so compiler will warn about missing cases */ + typestr = NULL; + switch (hba->conntype) + { + case ctLocal: + typestr = "local"; + break; + case ctHost: + typestr = "host"; + break; + case ctHostSSL: + typestr = "hostssl"; + break; + case ctHostNoSSL: + typestr = "hostnossl"; + break; + case ctHostGSS: + typestr = "hostgssenc"; + break; + case ctHostNoGSS: + typestr = "hostnogssenc"; + break; + } + if (typestr) + values[index++] = CStringGetTextDatum(typestr); + else + nulls[index++] = true; + + /* database */ + if (hba->databases) + { + /* + * Flatten HbaToken list to string list. It might seem that we + * should re-quote any quoted tokens, but that has been rejected + * on the grounds that it makes it harder to compare the array + * elements to other system catalogs. That makes entries like + * "all" or "samerole" formally ambiguous ... but users who name + * databases/roles that way are inflicting their own pain. + */ + List *names = NIL; + + foreach(lc, hba->databases) + { + HbaToken *tok = lfirst(lc); + + names = lappend(names, tok->string); + } + values[index++] = PointerGetDatum(strlist_to_textarray(names)); + } + else + nulls[index++] = true; + + /* user */ + if (hba->roles) + { + /* Flatten HbaToken list to string list; see comment above */ + List *roles = NIL; + + foreach(lc, hba->roles) + { + HbaToken *tok = lfirst(lc); + + roles = lappend(roles, tok->string); + } + values[index++] = PointerGetDatum(strlist_to_textarray(roles)); + } + else + nulls[index++] = true; + + /* address and netmask */ + /* Avoid a default: case so compiler will warn about missing cases */ + addrstr = maskstr = NULL; + switch (hba->ip_cmp_method) + { + case ipCmpMask: + if (hba->hostname) + { + addrstr = hba->hostname; + } + else + { + /* + * Note: if pg_getnameinfo_all fails, it'll set buffer to + * "???", which we want to return. + */ + if (hba->addrlen > 0) + { + if (pg_getnameinfo_all(&hba->addr, hba->addrlen, + buffer, sizeof(buffer), + NULL, 0, + NI_NUMERICHOST) == 0) + clean_ipv6_addr(hba->addr.ss_family, buffer); + addrstr = pstrdup(buffer); + } + if (hba->masklen > 0) + { + if (pg_getnameinfo_all(&hba->mask, hba->masklen, + buffer, sizeof(buffer), + NULL, 0, + NI_NUMERICHOST) == 0) + clean_ipv6_addr(hba->mask.ss_family, buffer); + maskstr = pstrdup(buffer); + } + } + break; + case ipCmpAll: + addrstr = "all"; + break; + case ipCmpSameHost: + addrstr = "samehost"; + break; + case ipCmpSameNet: + addrstr = "samenet"; + break; + } + if (addrstr) + values[index++] = CStringGetTextDatum(addrstr); + else + nulls[index++] = true; + if (maskstr) + values[index++] = CStringGetTextDatum(maskstr); + else + nulls[index++] = true; + + /* auth_method */ + values[index++] = CStringGetTextDatum(hba_authname(hba->auth_method)); + + /* options */ + options = gethba_options(hba); + if (options) + values[index++] = PointerGetDatum(options); + else + nulls[index++] = true; + } + else + { + /* no parsing result, so set relevant fields to nulls */ + memset(&nulls[1], true, (NUM_PG_HBA_FILE_RULES_ATTS - 2) * sizeof(bool)); + } + + /* error */ + if (err_msg) + values[NUM_PG_HBA_FILE_RULES_ATTS - 1] = CStringGetTextDatum(err_msg); + else + nulls[NUM_PG_HBA_FILE_RULES_ATTS - 1] = true; + + tuple = heap_form_tuple(tupdesc, values, nulls); + tuplestore_puttuple(tuple_store, tuple); +} + +/* + * Read the pg_hba.conf file and fill the tuplestore with view records. + */ +static void +fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc) +{ + FILE *file; + List *hba_lines = NIL; + ListCell *line; + MemoryContext linecxt; + MemoryContext hbacxt; + MemoryContext oldcxt; + + /* + * In the unlikely event that we can't open pg_hba.conf, we throw an + * error, rather than trying to report it via some sort of view entry. + * (Most other error conditions should result in a message in a view + * entry.) + */ + file = AllocateFile(HbaFileName, "r"); + if (file == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open configuration file \"%s\": %m", + HbaFileName))); + + linecxt = tokenize_auth_file(HbaFileName, file, &hba_lines, DEBUG3); + FreeFile(file); + + /* Now parse all the lines */ + hbacxt = AllocSetContextCreate(CurrentMemoryContext, + "hba parser context", + ALLOCSET_SMALL_SIZES); + oldcxt = MemoryContextSwitchTo(hbacxt); + foreach(line, hba_lines) + { + TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line); + HbaLine *hbaline = NULL; + + /* don't parse lines that already have errors */ + if (tok_line->err_msg == NULL) + hbaline = parse_hba_line(tok_line, DEBUG3); + + fill_hba_line(tuple_store, tupdesc, tok_line->line_num, + hbaline, tok_line->err_msg); + } + + /* Free tokenizer memory */ + MemoryContextDelete(linecxt); + /* Free parse_hba_line memory */ + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(hbacxt); +} + +/* + * SQL-accessible SRF to return all the entries in the pg_hba.conf file. + */ +Datum +pg_hba_file_rules(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsi; + + /* + * Build tuplestore to hold the result rows. We must use the Materialize + * mode to be safe against HBA file changes while the cursor is open. + * It's also more efficient than having to look up our current position in + * the parsed list every time. + */ + SetSingleFuncCall(fcinfo, 0); + + /* Fill the tuplestore */ + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + fill_hba_view(rsi->setResult, rsi->setDesc); + + PG_RETURN_NULL(); +} diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 8d9f3821b1..19924dca67 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -122,6 +122,16 @@ typedef struct HbaLine char *radiusports_s; } HbaLine; +/* + * A single string token lexed from a config file, together with whether + * the token had been quoted. + */ +typedef struct HbaToken +{ + char *string; + bool quoted; +} HbaToken; + typedef struct IdentLine { int linenumber; @@ -132,6 +142,22 @@ typedef struct IdentLine regex_t re; } IdentLine; +/* + * TokenizedAuthLine represents one line lexed from a config file. + * Each item in the "fields" list is a sub-list of HbaTokens. + * We don't emit a TokenizedAuthLine for empty or all-comment lines, + * so "fields" is never NIL (nor are any of its sub-lists). + * Exception: if an error occurs during tokenization, we might + * have fields == NIL, in which case err_msg != NULL. + */ +typedef struct TokenizedAuthLine +{ + List *fields; /* List of lists of HbaTokens */ + int line_num; /* Line number */ + char *raw_line; /* Raw line text */ + char *err_msg; /* Error message if any */ +} TokenizedAuthLine; + /* kluge to avoid including libpq/libpq-be.h here */ typedef struct Port hbaPort; @@ -142,6 +168,9 @@ extern void hba_getauthmethod(hbaPort *port); extern int check_usermap(const char *usermap_name, const char *pg_role, const char *auth_user, bool case_sensitive); +extern HbaLine *parse_hba_line(TokenizedAuthLine *tok_line, int elevel); extern bool pg_isblank(const char c); +extern MemoryContext tokenize_auth_file(const char *filename, FILE *file, + List **tok_lines, int elevel); #endif /* HBA_H */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index eaf3e7a8d4..109df9dc90 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2694,7 +2694,7 @@ ToastTupleContext ToastedAttribute TocEntry TokenAuxData -TokenizedLine +TokenizedAuthLine TrackItem TransInvalidationInfo TransState -- 2.33.1
>From 050caf9657ceca7251d98476f325fc2d67f6787b Mon Sep 17 00:00:00 2001 From: Julien Rouhaud <julien.rouh...@free.fr> Date: Mon, 21 Feb 2022 17:38:34 +0800 Subject: [PATCH v3 2/4] Add a pg_ident_file_mappings view. This view is similar to pg_hba_file_rules view, and can be also helpful to help diagnosing configuration problems. A following commit will add the possibility to include files in pg_hba and pg_ident configuration files, which will then make this view even more useful. Author: Julien Rouhaud Reviewed-by: FIXME Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya%40jrouhaud --- doc/src/sgml/catalogs.sgml | 108 ++++++++++++++++++++ doc/src/sgml/client-auth.sgml | 10 ++ doc/src/sgml/func.sgml | 5 +- src/backend/catalog/system_views.sql | 6 ++ src/backend/libpq/hba.c | 31 +++--- src/backend/utils/adt/hbafuncs.c | 136 +++++++++++++++++++++++++ src/include/catalog/pg_proc.dat | 7 ++ src/include/libpq/hba.h | 1 + src/test/regress/expected/rules.out | 6 ++ src/test/regress/expected/sysviews.out | 6 ++ src/test/regress/sql/sysviews.sql | 2 + 11 files changed, 302 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 7777d60514..e87f0543d6 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -9540,6 +9540,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l <entry>summary of client authentication configuration file contents</entry> </row> + <row> + <entry><link linkend="view-pg-hba-file-rules"><structname>pg_ident_file_mappings</structname></link></entry> + <entry>summary of client user name mapping configuration file contents</entry> + </row> + <row> <entry><link linkend="view-pg-indexes"><structname>pg_indexes</structname></link></entry> <entry>indexes</entry> @@ -10533,6 +10538,109 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </para> </sect1> + <sect1 id="view-pg-ident-file-mappings"> + <title><structname>pg_ident_file_mappings</structname></title> + + <indexterm zone="view-pg-ident-file-mappings"> + <primary>pg_ident_file_mappings</primary> + </indexterm> + + <para> + The view <structname>pg_ident_file_mappings</structname> provides a summary + of the contents of the client user name mapping configuration file, + <link linkend="auth-username-maps"><filename>pg_ident.conf</filename></link>. + A row appears in this view for each + non-empty, non-comment line in the file, with annotations indicating + whether the rule could be applied successfully. + </para> + + <para> + This view can be helpful for checking whether planned changes in the + authentication configuration file will work, or for diagnosing a previous + failure. Note that this view reports on the <emphasis>current</emphasis> + contents of the file, not on what was last loaded by the server. + </para> + + <para> + By default, the <structname>pg_ident_file_mappings</structname> view can be + read only by superusers. + </para> + + <table> + <title><structname>pg_ident_file_mappings</structname> Columns</title> <tgroup + cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>line_number</structfield> <type>int4</type> + </para> + <para> + Line number of this rule in <filename>pg_ident.conf</filename> + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>map_name</structfield> <type>text</type> + </para> + <para> + Name of the map + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>sys_name</structfield> <type>text</type> + </para> + <para> + Detected user name of the client + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>pg_username</structfield> <type>text</type> + </para> + <para> + Requested PostgreSQL user name + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>error</structfield> <type>text</type> + </para> + <para> + If not null, an error message indicating why this line could not be + processed + </para></entry> + </row> + </tbody> + </tgroup> + </table> + + <para> + Usually, a row reflecting an incorrect entry will have values for only + the <structfield>line_number</structfield> and <structfield>error</structfield> fields. + </para> + + <para> + See <xref linkend="client-authentication"/> for more information about + client authentication configuration. + </para> + </sect1> + <sect1 id="view-pg-indexes"> <title><structname>pg_indexes</structname></title> diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 02f0489112..142b0affcb 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -896,6 +896,16 @@ mymap /^(.*)@otherdomain\.com$ guest -HUP</literal>) to make it re-read the file. </para> + <para> + The system view + <link linkend="view-pg-ident-file-mappings"><structname>pg_ident_file_mappings</structname></link> + can be helpful for pre-testing changes to the + <filename>pg_ident.conf</filename> file, or for diagnosing problems if + loading of the file did not have the desired effects. Rows in the view with + non-null <structfield>error</structfield> fields indicate problems in the + corresponding lines of the file. + </para> + <para> A <filename>pg_ident.conf</filename> file that could be used in conjunction with the <filename>pg_hba.conf</filename> file in <xref diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 8a802fb225..b32cc61886 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -25475,8 +25475,9 @@ SELECT collation for ('foo' COLLATE "de_DE"); sending a <systemitem>SIGHUP</systemitem> signal to the postmaster process, which in turn sends <systemitem>SIGHUP</systemitem> to each of its children.) You can use the - <link linkend="view-pg-file-settings"><structname>pg_file_settings</structname></link> and - <link linkend="view-pg-hba-file-rules"><structname>pg_hba_file_rules</structname></link> views + <link linkend="view-pg-file-settings"><structname>pg_file_settings</structname></link>, + <link linkend="view-pg-hba-file-rules"><structname>pg_hba_file_rules</structname></link> and + <link linkend="view-pg-hba-file-rules"><structname>pg_ident_file_mappings</structname></link> views to check the configuration files for possible errors, before reloading. </para></entry> </row> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index bb1ac30cd1..90011f2d68 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -607,6 +607,12 @@ CREATE VIEW pg_hba_file_rules AS REVOKE ALL ON pg_hba_file_rules FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pg_hba_file_rules() FROM PUBLIC; +CREATE VIEW pg_ident_file_mappings AS + SELECT * FROM pg_ident_file_mappings() AS A; + +REVOKE ALL ON pg_ident_file_mappings FROM PUBLIC; +REVOKE EXECUTE ON FUNCTION pg_ident_file_mappings() FROM PUBLIC; + CREATE VIEW pg_timezone_abbrevs AS SELECT * FROM pg_timezone_abbrevs(); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index f9843a0b30..68136f6244 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -886,25 +886,22 @@ do { \ } while (0) /* - * Macros for handling pg_ident problems. - * Much as above, but currently the message level is hardwired as LOG - * and there is no provision for an err_msg string. + * Macros for handling pg_ident problems, similar as above. * * IDENT_FIELD_ABSENT: - * Log a message and exit the function if the given ident field ListCell is - * not populated. + * Reports when the given ident field ListCell is not populated. * * IDENT_MULTI_VALUE: - * Log a message and exit the function if the given ident token List has more - * than one element. + * Reports when the given ident token List has more than one element. */ #define IDENT_FIELD_ABSENT(field) \ do { \ if (!field) { \ - ereport(LOG, \ + ereport(elevel, \ (errcode(ERRCODE_CONFIG_FILE_ERROR), \ errmsg("missing entry in file \"%s\" at end of line %d", \ IdentFileName, line_num))); \ + *err_msg = psprintf("missing entry at end of line"); \ return NULL; \ } \ } while (0) @@ -912,11 +909,12 @@ do { \ #define IDENT_MULTI_VALUE(tokens) \ do { \ if (tokens->length > 1) { \ - ereport(LOG, \ + ereport(elevel, \ (errcode(ERRCODE_CONFIG_FILE_ERROR), \ errmsg("multiple values in ident field"), \ errcontext("line %d of configuration file \"%s\"", \ line_num, IdentFileName))); \ + *err_msg = psprintf("multiple values in ident field"); \ return NULL; \ } \ } while (0) @@ -2305,7 +2303,8 @@ load_hba(void) * Parse one tokenised line from the ident config file and store the result in * an IdentLine structure. * - * If parsing fails, log a message and return NULL. + * If parsing fails, log a message at ereport level elevel, store an error + * string in tok_line->err_msg and return NULL. * * If ident_user is a regular expression (ie. begins with a slash), it is * compiled and stored in IdentLine structure. @@ -2314,10 +2313,11 @@ load_hba(void) * to have set a memory context that will be reset if this function returns * NULL. */ -static IdentLine * -parse_ident_line(TokenizedAuthLine *tok_line) +IdentLine * +parse_ident_line(TokenizedAuthLine *tok_line, int elevel) { int line_num = tok_line->line_num; + char **err_msg = &tok_line->err_msg; ListCell *field; List *tokens; HbaToken *token; @@ -2371,11 +2371,14 @@ parse_ident_line(TokenizedAuthLine *tok_line) char errstr[100]; pg_regerror(r, &parsedline->re, errstr, sizeof(errstr)); - ereport(LOG, + ereport(elevel, (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), errmsg("invalid regular expression \"%s\": %s", parsedline->ident_user + 1, errstr))); + *err_msg = psprintf("invalid regular expression \"%s\": %s", + parsedline->ident_user + 1, errstr); + pfree(wstr); return NULL; } @@ -2626,7 +2629,7 @@ load_ident(void) continue; } - if ((newline = parse_ident_line(tok_line)) == NULL) + if ((newline = parse_ident_line(tok_line, LOG)) == NULL) { /* Parse error; remember there's trouble */ ok = false; diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c index f230bad8b6..75e69383c2 100644 --- a/src/backend/utils/adt/hbafuncs.c +++ b/src/backend/utils/adt/hbafuncs.c @@ -28,6 +28,9 @@ static ArrayType *gethba_options(HbaLine *hba); static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, int lineno, HbaLine *hba, const char *err_msg); static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc); +static void fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, + int lineno, IdentLine *ident, const char *err_msg); +static void fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc); /* @@ -421,3 +424,136 @@ pg_hba_file_rules(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } + +/* Number of columns in pg_hba_file_mappings view */ +#define NUM_PG_IDENT_FILE_MAPPINGS_ATTS 5 + +/* + * fill_ident_line: build one row of pg_ident_file_mappings view, add it to + * tuplestore + * + * tuple_store: where to store data + * tupdesc: tuple descriptor for the view + * lineno: pg_hba.conf line number (must always be valid) + * ident: parsed line data (can be NULL, in which case err_msg should be set) + * err_msg: error message (NULL if none) + * + * Note: leaks memory, but we don't care since this is run in a short-lived + * memory context. + */ +static void +fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, + int lineno, IdentLine *ident, const char *err_msg) +{ + Datum values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS]; + bool nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS]; + HeapTuple tuple; + int index; + + Assert(tupdesc->natts == NUM_PG_IDENT_FILE_MAPPINGS_ATTS); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + index = 0; + + /* line_number */ + values[index++] = Int32GetDatum(lineno); + + if (ident != NULL) + { + values[index++] = CStringGetTextDatum(ident->usermap); + values[index++] = CStringGetTextDatum(ident->ident_user); + values[index++] = CStringGetTextDatum(ident->pg_role); + } + else + { + /* no parsing result, so set relevant fields to nulls */ + memset(&nulls[1], true, (NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 2) * sizeof(bool)); + } + + /* error */ + if (err_msg) + values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = CStringGetTextDatum(err_msg); + else + nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = true; + + tuple = heap_form_tuple(tupdesc, values, nulls); + tuplestore_puttuple(tuple_store, tuple); +} + +/* + * Read the pg_ident.conf file and fill the tuplestore with view records. + */ +static void +fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc) +{ + FILE *file; + List *ident_lines = NIL; + ListCell *line; + MemoryContext linecxt; + MemoryContext identcxt; + MemoryContext oldcxt; + + /* + * In the unlikely event that we can't open pg_hba.conf, we throw an + * error, rather than trying to report it via some sort of view entry. + * (Most other error conditions should result in a message in a view + * entry.) + */ + file = AllocateFile(IdentFileName, "r"); + if (file == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open usermap file \"%s\": %m", + IdentFileName))); + + linecxt = tokenize_auth_file(HbaFileName, file, &ident_lines, DEBUG3); + FreeFile(file); + + /* Now parse all the lines */ + identcxt = AllocSetContextCreate(CurrentMemoryContext, + "ident parser context", + ALLOCSET_SMALL_SIZES); + oldcxt = MemoryContextSwitchTo(identcxt); + foreach(line, ident_lines) + { + TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line); + IdentLine *identline = NULL; + + /* don't parse lines that already have errors */ + if (tok_line->err_msg == NULL) + identline = parse_ident_line(tok_line, DEBUG3); + + fill_ident_line(tuple_store, tupdesc, tok_line->line_num, identline, + tok_line->err_msg); + } + + /* Free tokenizer memory */ + MemoryContextDelete(linecxt); + /* Free parse_hba_line memory */ + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(identcxt); +} + +/* + * SQL-accessible SRF to return all the entries in the pg_ident.conf file. + */ +Datum +pg_ident_file_mappings(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsi; + + /* + * Build tuplestore to hold the result rows. We must use the Materialize + * mode to be safe against HBA file changes while the cursor is open. + * It's also more efficient than having to look up our current position in + * the parsed list every time. + */ + SetSingleFuncCall(fcinfo, 0); + + /* Fill the tuplestore */ + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + fill_ident_view(rsi->setResult, rsi->setDesc); + + PG_RETURN_NULL(); +} diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index d8e8715ed1..6ccbc9af4c 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6115,6 +6115,13 @@ proargmodes => '{o,o,o,o,o,o,o,o,o}', proargnames => '{line_number,type,database,user_name,address,netmask,auth_method,options,error}', prosrc => 'pg_hba_file_rules' }, +{ oid => '9556', descr => 'show pg_ident.conf mappings', + proname => 'pg_ident_file_mappings', prorows => '1000', proretset => 't', + provolatile => 'v', prorettype => 'record', proargtypes => '', + proallargtypes => '{int4,text,text,text,text}', + proargmodes => '{o,o,o,o,o}', + proargnames => '{line_number,map_name,sys_name,pg_usernamee,error}', + prosrc => 'pg_ident_file_mappings' }, { oid => '1371', descr => 'view system lock information', proname => 'pg_lock_status', prorows => '1000', proretset => 't', provolatile => 'v', prorettype => 'record', proargtypes => '', diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 19924dca67..fce7db248b 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -169,6 +169,7 @@ extern int check_usermap(const char *usermap_name, const char *pg_role, const char *auth_user, bool case_sensitive); extern HbaLine *parse_hba_line(TokenizedAuthLine *tok_line, int elevel); +extern IdentLine *parse_ident_line(TokenizedAuthLine *tok_line, int elevel); extern bool pg_isblank(const char c); extern MemoryContext tokenize_auth_file(const char *filename, FILE *file, List **tok_lines, int elevel); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index ac468568a1..76a209b717 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1347,6 +1347,12 @@ pg_hba_file_rules| SELECT a.line_number, a.options, a.error FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error); +pg_ident_file_mappings| SELECT a.line_number, + a.map_name, + a.sys_name, + a.pg_usernamee, + a.error + FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_usernamee, error); pg_indexes| SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out index 442eeb1e3f..31ba549883 100644 --- a/src/test/regress/expected/sysviews.out +++ b/src/test/regress/expected/sysviews.out @@ -55,6 +55,12 @@ select count(*) > 0 as ok from pg_hba_file_rules; t (1 row) +select count(*) >= 0 as ok from pg_ident_file_mappings; + ok +---- + t +(1 row) + -- There will surely be at least one active lock select count(*) > 0 as ok from pg_locks; ok diff --git a/src/test/regress/sql/sysviews.sql b/src/test/regress/sql/sysviews.sql index 4980f07be2..1148014e47 100644 --- a/src/test/regress/sql/sysviews.sql +++ b/src/test/regress/sql/sysviews.sql @@ -28,6 +28,8 @@ select count(*) >= 0 as ok from pg_file_settings; -- There will surely be at least one rule select count(*) > 0 as ok from pg_hba_file_rules; +select count(*) >= 0 as ok from pg_ident_file_mappings; + -- There will surely be at least one active lock select count(*) > 0 as ok from pg_locks; -- 2.33.1
>From d0fdc3c1443802087fd58259729ee83128751056 Mon Sep 17 00:00:00 2001 From: Julien Rouhaud <julien.rouh...@free.fr> Date: Mon, 21 Feb 2022 15:45:26 +0800 Subject: [PATCH v3 3/4] Allow file inclusion in pg_hba and pg_ident files. Author: Julien Rouhaud Reviewed-by: FIXME Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya%40jrouhaud --- doc/src/sgml/catalogs.sgml | 48 +++++- doc/src/sgml/client-auth.sgml | 34 +++- src/backend/libpq/hba.c | 218 +++++++++++++++++++------ src/backend/libpq/pg_hba.conf.sample | 8 +- src/backend/libpq/pg_ident.conf.sample | 8 +- src/backend/utils/adt/hbafuncs.c | 51 ++++-- src/include/catalog/pg_proc.dat | 12 +- src/include/libpq/hba.h | 2 + src/test/regress/expected/rules.out | 12 +- 9 files changed, 314 insertions(+), 79 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index e87f0543d6..c6c5e7d08e 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -10440,12 +10440,31 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </thead> <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>rule_number</structfield> <type>int4</type> + </para> + <para> + Rule number, in priority order, of this rule if the rule is valid, + otherwise null + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>file_name</structfield> <type>text</type> + </para> + <para> + File name of this rule + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>line_number</structfield> <type>int4</type> </para> <para> - Line number of this rule in <filename>pg_hba.conf</filename> + Line number of this rule in the given file_name </para></entry> </row> @@ -10581,6 +10600,33 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </thead> <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>mapping_number</structfield> <type>int4</type> + </para> + <para> + Rule number, in priority order, of this mapping if the mapping is valid, + otherwise null + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>file_name</structfield> <type>text</type> + </para> + <para> + File name of this mapping + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>line_number</structfield> <type>int4</type> + </para> + <para> + Line number of this mapping in the given file_name + </para></entry> + </row> <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>line_number</structfield> <type>int4</type> diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 142b0affcb..e1d0e103b3 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -89,8 +89,17 @@ </para> <para> - Each record specifies a connection type, a client IP address range - (if relevant for the connection type), a database name, a user name, + Each record can either be an inclusion directive or an authentication rule. + Inclusion records specifies files that can be included, which contains + additional records. The records will be inserted in lieu of the inclusion + records. Those records only contains two fields: the + <literal>include</literal> directive and the file to be included. The file + can be a relative of absolute path, and can be double quoted if needed. + </para> + + <para> + Each authentication record specifies a connection type, a client IP address + range (if relevant for the connection type), a database name, a user name, and the authentication method to be used for connections matching these parameters. The first record with a matching connection type, client address, requested database, and user name is used to perform @@ -103,6 +112,7 @@ <para> A record can have several formats: <synopsis> +include <replaceable>file</replaceable> local <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>address</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>address</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional> @@ -118,6 +128,15 @@ hostnogssenc <replaceable>database</replaceable> <replaceable>user</replaceabl The meaning of the fields is as follows: <variablelist> + <varlistentry> + <term><literal>include</literal></term> + <listitem> + <para> + This line will be replaced with the content of the given file. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><literal>local</literal></term> <listitem> @@ -835,8 +854,9 @@ local db1,db2,@demodbs all md5 cluster's data directory. (It is possible to place the map file elsewhere, however; see the <xref linkend="guc-ident-file"/> configuration parameter.) - The ident map file contains lines of the general form: + The ident map file contains lines of two general form: <synopsis> +<replaceable>include</replaceable> <replaceable>file</replaceable> <replaceable>map-name</replaceable> <replaceable>system-username</replaceable> <replaceable>database-username</replaceable> </synopsis> Comments, whitespace and line continuations are handled in the same way as in @@ -847,6 +867,14 @@ local db1,db2,@demodbs all md5 database user name. The same <replaceable>map-name</replaceable> can be used repeatedly to specify multiple user-mappings within a single map. </para> + <para> + The lines can record can either be an inclusion directive or an authentication rule. + Inclusion records specifies files that can be included, which contains + additional records. The records will be inserted in lieu of the inclusion + records. Those records only contains two fields: the + <literal>include</literal> directive and the file to be included. The file + can be a relative of absolute path, and can be double quoted if needed. + </para> <para> There is no restriction regarding how many database users a given operating system user can correspond to, nor vice versa. Thus, entries diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 68136f6244..1bfcaef025 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -68,6 +68,12 @@ typedef struct check_network_data #define token_is_keyword(t, k) (!t->quoted && strcmp(t->string, k) == 0) #define token_matches(t, k) (strcmp(t->string, k) == 0) +typedef enum HbaIncludeKind +{ + SecondaryAuthFile, + IncludedAuthFile +} HbaIncludeKind; + /* * pre-parsed content of HBA config file: list of HbaLine structs. * parsed_hba_context is the memory context where it lives. @@ -112,10 +118,16 @@ static const char *const UserAuthName[] = }; +static void tokenize_file_with_context(MemoryContext linecxt, + const char *filename, FILE *file, + List **tok_lines, int elevel); 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 FILE *open_inc_file(HbaIncludeKind kind, const char *inc_filename, + const char *outer_filename, int elevel, + char **err_msg, char **inc_fullname); /* @@ -355,36 +367,11 @@ tokenize_inc_file(List *tokens, ListCell *inc_line; MemoryContext linecxt; - if (is_absolute_path(inc_filename)) - { - /* absolute path is taken as-is */ - inc_fullname = pstrdup(inc_filename); - } - else - { - /* relative path is relative to dir of calling file */ - inc_fullname = (char *) palloc(strlen(outer_filename) + 1 + - strlen(inc_filename) + 1); - strcpy(inc_fullname, outer_filename); - get_parent_directory(inc_fullname); - join_path_components(inc_fullname, inc_fullname, inc_filename); - canonicalize_path(inc_fullname); - } + inc_file = open_inc_file(SecondaryAuthFile, inc_filename, outer_filename, + elevel, err_msg, &inc_fullname); - inc_file = AllocateFile(inc_fullname, "r"); if (inc_file == NULL) - { - int save_errno = errno; - - ereport(elevel, - (errcode_for_file_access(), - errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m", - inc_filename, inc_fullname))); - *err_msg = psprintf("could not open secondary authentication file \"@%s\" as \"%s\": %s", - inc_filename, inc_fullname, strerror(save_errno)); - pfree(inc_fullname); return tokens; - } /* There is possible recursion here if the file contains @ */ linecxt = tokenize_auth_file(inc_fullname, inc_file, &inc_lines, elevel); @@ -423,12 +410,38 @@ tokenize_inc_file(List *tokens, return tokens; } +/* + * Tokenize the given file. + * + * Wrapper around tokenize_file_with_context, creating a decicated memory + * context. + * + * Return value is this memory context which contains all memory allocated by + * this function (it's a child of caller's context). + */ +MemoryContext +tokenize_auth_file(const char *filename, FILE *file, List **tok_lines, int elevel) +{ + MemoryContext linecxt; + linecxt = AllocSetContextCreate(CurrentMemoryContext, + "tokenize_auth_file", + ALLOCSET_SMALL_SIZES); + + *tok_lines = NIL; + + tokenize_file_with_context(linecxt, filename, file, tok_lines, elevel); + + return linecxt; +} + /* * Tokenize the given file. * * The output is a list of TokenizedAuthLine structs; see struct definition * above. * + * linecxt: memory context which must contain all memory allocated by the + * function * filename: the absolute path to the target file * file: the already-opened target file * tok_lines: receives output list @@ -437,30 +450,22 @@ tokenize_inc_file(List *tokens, * Errors are reported by logging messages at ereport level elevel and by * adding TokenizedAuthLine structs containing non-null err_msg fields to the * output list. - * - * Return value is a memory context which contains all memory allocated by - * this function (it's a child of caller's context). */ -MemoryContext -tokenize_auth_file(const char *filename, FILE *file, List **tok_lines, - int elevel) +static void +tokenize_file_with_context(MemoryContext linecxt, const char *filename, + FILE *file, List **tok_lines, int elevel) { - int line_number = 1; StringInfoData buf; - MemoryContext linecxt; + int line_number = 1; MemoryContext oldcxt; - linecxt = AllocSetContextCreate(CurrentMemoryContext, - "tokenize_auth_file", - ALLOCSET_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(linecxt); initStringInfo(&buf); - *tok_lines = NIL; - while (!feof(file) && !ferror(file)) { + TokenizedAuthLine *tok_line; char *lineptr; List *current_line = NIL; char *err_msg = NULL; @@ -521,29 +526,76 @@ tokenize_auth_file(const char *filename, FILE *file, List **tok_lines, } /* - * Reached EOL; emit line to TokenizedAuthLine list unless it's boring + * Reached EOL; no need to emit line to TokenizedAuthLine list if it's + * boring. */ - if (current_line != NIL || err_msg != NULL) + if (current_line == NIL && err_msg == NULL) + goto next_line; + + /* If the line is valid, check if that's an include directive */ + if (err_msg == NULL && list_length(current_line) == 2) { - TokenizedAuthLine *tok_line; + HbaToken *first, *second; + + first = linitial(linitial_node(List, current_line)); + second = linitial(lsecond_node(List, current_line)); - tok_line = (TokenizedAuthLine *) palloc(sizeof(TokenizedAuthLine)); - tok_line->fields = current_line; - tok_line->line_num = line_number; - tok_line->raw_line = pstrdup(buf.data); - tok_line->err_msg = err_msg; - *tok_lines = lappend(*tok_lines, tok_line); + if (strcmp(first->string, "include") == 0) + { + char *inc_filename; + char *inc_fullname; + FILE *inc_file; + + inc_filename = second->string; + + inc_file = open_inc_file(IncludedAuthFile, inc_filename, + filename, elevel, &err_msg, + &inc_fullname); + + /* + * The included file could be open, now recursively process it. + * Errors will be reported in the general TokenizedAuthLine + * processing. + */ + if (inc_file != NULL) + { + tokenize_file_with_context(linecxt, inc_fullname, inc_file, + tok_lines, elevel); + + FreeFile(inc_file); + pfree(inc_fullname); + + /* + * The line is fully processed, bypass the general + * TokenizedAuthLine processing. + */ + goto next_line; + } + else + { + /* We should got an error */ + Assert(err_msg != NULL); + } + } } + /* General processing: emit line to the TokenizedAuthLine */ + tok_line = (TokenizedAuthLine *) palloc(sizeof(TokenizedAuthLine)); + tok_line->fields = current_line; + tok_line->file_name = pstrdup(filename); + tok_line->line_num = line_number; + tok_line->raw_line = pstrdup(buf.data); + tok_line->err_msg = err_msg; + *tok_lines = lappend(*tok_lines, tok_line); + +next_line: line_number += continuations + 1; + } MemoryContextSwitchTo(oldcxt); - - return linecxt; } - /* * Does user belong to role? * @@ -950,6 +1002,7 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel) HbaLine *parsedline; parsedline = palloc0(sizeof(HbaLine)); + parsedline->sourcefile = pstrdup(tok_line->file_name); parsedline->linenumber = line_num; parsedline->rawline = pstrdup(tok_line->raw_line); @@ -2298,6 +2351,67 @@ load_hba(void) return true; } +/* + * Open the given file for inclusion in an authentication file, whether + * secondary or included. + */ +static FILE * +open_inc_file(HbaIncludeKind kind, const char *inc_filename, + const char *outer_filename, int elevel, char **err_msg, + char **inc_fullname) +{ + FILE *inc_file; + + if (is_absolute_path(inc_filename)) + { + /* absolute path is taken as-is */ + *inc_fullname = pstrdup(inc_filename); + } + else + { + /* relative path is relative to dir of calling file */ + *inc_fullname = (char *) palloc(strlen(outer_filename) + 1 + + strlen(inc_filename) + 1); + strcpy(*inc_fullname, outer_filename); + get_parent_directory(*inc_fullname); + join_path_components(*inc_fullname, *inc_fullname, inc_filename); + canonicalize_path(*inc_fullname); + } + + inc_file = AllocateFile(*inc_fullname, "r"); + if (inc_file == NULL) + { + int save_errno = errno; + const char *msglog; + const char *msgview; + + switch (kind) + { + case SecondaryAuthFile: + msglog = "could not open secondary authentication file \"@%s\" as \"%s\": %m"; + msgview = "could not open secondary authentication file \"@%s\" as \"%s\": %s"; + break; + case IncludedAuthFile: + msglog = "could not open included authentication file \"%s\" as \"%s\": %m"; + msgview = "could not open included authentication file \"%s\" as \"%s\": %s"; + break; + default: + elog(ERROR, "unknown HbaIncludeKind: %d", kind); + break; + } + + ereport(elevel, + (errcode_for_file_access(), + errmsg(msglog, inc_filename, *inc_fullname))); + *err_msg = psprintf(msgview, inc_filename, *inc_fullname, + strerror(save_errno)); + pfree(*inc_fullname); + *inc_fullname = NULL; + return NULL; + } + + return inc_file; +} /* * Parse one tokenised line from the ident config file and store the result in diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample index 5f3f63eb0c..0b6589a7b9 100644 --- a/src/backend/libpq/pg_hba.conf.sample +++ b/src/backend/libpq/pg_hba.conf.sample @@ -9,6 +9,7 @@ # are authenticated, which PostgreSQL user names they can use, which # databases they can access. Records take one of these forms: # +# include FILE # local DATABASE USER METHOD [OPTIONS] # host DATABASE USER ADDRESS METHOD [OPTIONS] # hostssl DATABASE USER ADDRESS METHOD [OPTIONS] @@ -18,7 +19,12 @@ # # (The uppercase items must be replaced by actual values.) # -# The first field is the connection type: +# If the first field is "include", it's not a mapping record but a directive to +# include records from another file, specified in the field. FILE is the file +# to include. It can be specified with a relative or absolute path, and can be +# double quoted if it contains spaces. +# +# Otherwise the first field is the connection type: # - "local" is a Unix-domain socket # - "host" is a TCP/IP socket (encrypted or not) # - "hostssl" is a TCP/IP socket that is SSL-encrypted diff --git a/src/backend/libpq/pg_ident.conf.sample b/src/backend/libpq/pg_ident.conf.sample index a5870e6448..138359cf03 100644 --- a/src/backend/libpq/pg_ident.conf.sample +++ b/src/backend/libpq/pg_ident.conf.sample @@ -7,12 +7,18 @@ # # This file controls PostgreSQL user name mapping. It maps external # user names to their corresponding PostgreSQL user names. Records -# are of the form: +# are one of these forms: # +# include FILE # MAPNAME SYSTEM-USERNAME PG-USERNAME # # (The uppercase quantities must be replaced by actual values.) # +# If the first field is "include", it's not an authentication record but a +# directive to include records from another file, specified in the field. FILE +# is the file to include. It can be specified with a relative or absolute +# path, and can be double quoted if it contains spaces. +# # MAPNAME is the (otherwise freely chosen) map name that was used in # pg_hba.conf. SYSTEM-USERNAME is the detected user name of the # client. PG-USERNAME is the requested PostgreSQL user name. The diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c index 75e69383c2..ee12dc1893 100644 --- a/src/backend/utils/adt/hbafuncs.c +++ b/src/backend/utils/adt/hbafuncs.c @@ -26,9 +26,11 @@ static ArrayType *gethba_options(HbaLine *hba); static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, - int lineno, HbaLine *hba, const char *err_msg); + int rule_number, const char *filename, int lineno, + HbaLine *hba, const char *err_msg); static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc); static void fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, + int mapping_number, const char *filename, int lineno, IdentLine *ident, const char *err_msg); static void fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc); @@ -157,7 +159,7 @@ gethba_options(HbaLine *hba) } /* Number of columns in pg_hba_file_rules view */ -#define NUM_PG_HBA_FILE_RULES_ATTS 9 +#define NUM_PG_HBA_FILE_RULES_ATTS 11 /* * fill_hba_line: build one row of pg_hba_file_rules view, add it to tuplestore @@ -173,7 +175,8 @@ gethba_options(HbaLine *hba) */ static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, - int lineno, HbaLine *hba, const char *err_msg) + int rule_number, const char *filename, int lineno, HbaLine *hba, + const char *err_msg) { Datum values[NUM_PG_HBA_FILE_RULES_ATTS]; bool nulls[NUM_PG_HBA_FILE_RULES_ATTS]; @@ -192,6 +195,13 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, memset(nulls, 0, sizeof(nulls)); index = 0; + /* rule_number */ + if (err_msg) + nulls[index++] = true; + else + values[index++] = Int32GetDatum(rule_number); + /* file_name */ + values[index++] = CStringGetTextDatum(filename); /* line_number */ values[index++] = Int32GetDatum(lineno); @@ -335,7 +345,7 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, else { /* no parsing result, so set relevant fields to nulls */ - memset(&nulls[1], true, (NUM_PG_HBA_FILE_RULES_ATTS - 2) * sizeof(bool)); + memset(&nulls[3], true, (NUM_PG_HBA_FILE_RULES_ATTS - 4) * sizeof(bool)); } /* error */ @@ -357,6 +367,7 @@ fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc) FILE *file; List *hba_lines = NIL; ListCell *line; + int rule_number = 0; MemoryContext linecxt; MemoryContext hbacxt; MemoryContext oldcxt; @@ -391,8 +402,12 @@ fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc) if (tok_line->err_msg == NULL) hbaline = parse_hba_line(tok_line, DEBUG3); - fill_hba_line(tuple_store, tupdesc, tok_line->line_num, - hbaline, tok_line->err_msg); + /* No error, set a rule number */ + if (tok_line->err_msg == NULL) + rule_number++; + + fill_hba_line(tuple_store, tupdesc, rule_number, tok_line->file_name, + tok_line->line_num, hbaline, tok_line->err_msg); } /* Free tokenizer memory */ @@ -426,7 +441,7 @@ pg_hba_file_rules(PG_FUNCTION_ARGS) } /* Number of columns in pg_hba_file_mappings view */ -#define NUM_PG_IDENT_FILE_MAPPINGS_ATTS 5 +#define NUM_PG_IDENT_FILE_MAPPINGS_ATTS 7 /* * fill_ident_line: build one row of pg_ident_file_mappings view, add it to @@ -443,7 +458,8 @@ pg_hba_file_rules(PG_FUNCTION_ARGS) */ static void fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, - int lineno, IdentLine *ident, const char *err_msg) + int mapping_number, const char *filename, int lineno, + IdentLine *ident, const char *err_msg) { Datum values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS]; bool nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS]; @@ -456,6 +472,13 @@ fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, memset(nulls, 0, sizeof(nulls)); index = 0; + /* mapping_number */ + if (err_msg) + nulls[index++] = true; + else + values[index++] = Int32GetDatum(mapping_number); + /* file_name */ + values[index++] = CStringGetTextDatum(filename); /* line_number */ values[index++] = Int32GetDatum(lineno); @@ -468,7 +491,7 @@ fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc, else { /* no parsing result, so set relevant fields to nulls */ - memset(&nulls[1], true, (NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 2) * sizeof(bool)); + memset(&nulls[3], true, (NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 4) * sizeof(bool)); } /* error */ @@ -490,6 +513,7 @@ fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc) FILE *file; List *ident_lines = NIL; ListCell *line; + int mapping_number = 0; MemoryContext linecxt; MemoryContext identcxt; MemoryContext oldcxt; @@ -524,8 +548,13 @@ fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc) if (tok_line->err_msg == NULL) identline = parse_ident_line(tok_line, DEBUG3); - fill_ident_line(tuple_store, tupdesc, tok_line->line_num, identline, - tok_line->err_msg); + /* No error, set a rule number */ + if (tok_line->err_msg == NULL) + mapping_number++; + + fill_ident_line(tuple_store, tupdesc, mapping_number, + tok_line->file_name, tok_line->line_num, identline, + tok_line->err_msg); } /* Free tokenizer memory */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 6ccbc9af4c..0e8a589302 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6111,16 +6111,16 @@ { oid => '3401', descr => 'show pg_hba.conf rules', proname => 'pg_hba_file_rules', prorows => '1000', proretset => 't', provolatile => 'v', prorettype => 'record', proargtypes => '', - proallargtypes => '{int4,text,_text,_text,text,text,text,_text,text}', - proargmodes => '{o,o,o,o,o,o,o,o,o}', - proargnames => '{line_number,type,database,user_name,address,netmask,auth_method,options,error}', + proallargtypes => '{int4,text,int4,text,_text,_text,text,text,text,_text,text}', + proargmodes => '{o,o,o,o,o,o,o,o,o,o,o}', + proargnames => '{rule_number,file_name,line_number,type,database,user_name,address,netmask,auth_method,options,error}', prosrc => 'pg_hba_file_rules' }, { oid => '9556', descr => 'show pg_ident.conf mappings', proname => 'pg_ident_file_mappings', prorows => '1000', proretset => 't', provolatile => 'v', prorettype => 'record', proargtypes => '', - proallargtypes => '{int4,text,text,text,text}', - proargmodes => '{o,o,o,o,o}', - proargnames => '{line_number,map_name,sys_name,pg_usernamee,error}', + proallargtypes => '{int4,text,int4,text,text,text,text}', + proargmodes => '{o,o,o,o,o,o,o}', + proargnames => '{mapping_number,file_name,line_number,map_name,sys_name,pg_usernamee,error}', prosrc => 'pg_ident_file_mappings' }, { oid => '1371', descr => 'view system lock information', proname => 'pg_lock_status', prorows => '1000', proretset => 't', diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index fce7db248b..551e961585 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -79,6 +79,7 @@ typedef enum ClientCertName typedef struct HbaLine { + char *sourcefile; int linenumber; char *rawline; ConnType conntype; @@ -153,6 +154,7 @@ typedef struct IdentLine typedef struct TokenizedAuthLine { List *fields; /* List of lists of HbaTokens */ + char *file_name; /* File name */ int line_num; /* Line number */ char *raw_line; /* Raw line text */ char *err_msg; /* Error message if any */ diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 76a209b717..5b7a4c01ee 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1337,7 +1337,9 @@ pg_group| SELECT pg_authid.rolname AS groname, WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid WHERE (NOT pg_authid.rolcanlogin); -pg_hba_file_rules| SELECT a.line_number, +pg_hba_file_rules| SELECT a.rule_number, + a.file_name, + a.line_number, a.type, a.database, a.user_name, @@ -1346,13 +1348,15 @@ pg_hba_file_rules| SELECT a.line_number, a.auth_method, a.options, a.error - FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error); -pg_ident_file_mappings| SELECT a.line_number, + FROM pg_hba_file_rules() a(rule_number, file_name, line_number, type, database, user_name, address, netmask, auth_method, options, error); +pg_ident_file_mappings| SELECT a.mapping_number, + a.file_name, + a.line_number, a.map_name, a.sys_name, a.pg_usernamee, a.error - FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, pg_usernamee, error); + FROM pg_ident_file_mappings() a(mapping_number, file_name, line_number, map_name, sys_name, pg_usernamee, error); pg_indexes| SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, -- 2.33.1
>From 711359a1a80a7bfcf350b9ffa0a2f869f15a7e81 Mon Sep 17 00:00:00 2001 From: Julien Rouhaud <julien.rouh...@free.fr> Date: Tue, 22 Feb 2022 21:34:54 +0800 Subject: [PATCH v3 4/4] POC: Add a pg_hba_matches() function. Author: Julien Rouhaud Reviewed-by: FIXME Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya%40jrouhaud --- src/backend/catalog/system_functions.sql | 9 ++ src/backend/libpq/hba.c | 138 +++++++++++++++++++++++ src/include/catalog/pg_proc.dat | 7 ++ 3 files changed, 154 insertions(+) diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index 81bac6f581..049cdabc81 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -594,6 +594,15 @@ LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE AS 'unicode_is_normalized'; +CREATE OR REPLACE FUNCTION + pg_hba_matches( + IN address inet, IN role text, IN ssl bool DEFAULT false, + OUT file_name text, OUT line_num int4, OUT raw_line text) +RETURNS RECORD +LANGUAGE INTERNAL +VOLATILE +AS 'pg_hba_matches'; + -- -- The default permissions for functions mean that anyone can execute them. -- A number of functions shouldn't be executable by just anyone, but rather diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 1bfcaef025..8afbb16b76 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -26,6 +26,7 @@ #include <unistd.h> #include "access/htup_details.h" +#include "catalog/pg_authid.h" #include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "common/ip.h" @@ -41,6 +42,7 @@ #include "utils/acl.h" #include "utils/builtins.h" #include "utils/guc.h" +#include "utils/inet.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/varlena.h" @@ -2833,3 +2835,139 @@ hba_authname(UserAuth auth_method) return UserAuthName[auth_method]; } + +#define PG_HBA_MATCHES_ATTS 3 + +/* + * SQL-accessible SRF to return the entries that match the given connection + * info, if any. + */ +Datum pg_hba_matches(PG_FUNCTION_ARGS) +{ + MemoryContext ctxt; + inet *address = NULL; + bool ssl_in_use = false; + hbaPort *port = palloc0(sizeof(hbaPort)); + TupleDesc tupdesc; + Datum values[PG_HBA_MATCHES_ATTS]; + bool isnull[PG_HBA_MATCHES_ATTS]; + + if (!is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("only superuser or a member of the pg_read_server_files role may call this function"))); + + if (PG_ARGISNULL(0)) + port->raddr.addr.ss_family = AF_UNIX; + else + { + int bits; + char *ptr; + char tmp[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255/128")]; + + address = PG_GETARG_INET_PP(0); + + bits = ip_maxbits(address) - ip_bits(address); + if (bits != 0) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid address"))); + } + + /* force display of max bits, regardless of masklen... */ + if (pg_inet_net_ntop(ip_family(address), ip_addr(address), + ip_maxbits(address), tmp, sizeof(tmp)) == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("could not format inet value: %m"))); + + /* Suppress /n if present (shouldn't happen now) */ + if ((ptr = strchr(tmp, '/')) != NULL) + *ptr = '\0'; + + switch (ip_family(address)) + { + case PGSQL_AF_INET: + { + struct sockaddr_in *dst; + + dst = (struct sockaddr_in *) &port->raddr.addr; + dst->sin_family = AF_INET; + + /* ip_addr(address) always contains network representation */ + memcpy(&dst->sin_addr, &ip_addr(address), sizeof(dst->sin_addr)); + + break; + } + /* See pg_inet_net_ntop() for details about those constants */ + case PGSQL_AF_INET6: +#if defined(AF_INET6) && AF_INET6 != PGSQL_AF_INET6 + case AF_INET6: +#endif + { + struct sockaddr_in6 *dst; + + dst = (struct sockaddr_in6 *) &port->raddr.addr; + dst->sin6_family = AF_INET6; + + /* ip_addr(address) always contains network representation */ + memcpy(&dst->sin6_addr, &ip_addr(address), sizeof(dst->sin6_addr)); + + break; + } + default: + elog(ERROR, "unexpected ip_family: %d", ip_family(address)); + break; + } + } + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter role is mandatory"))); + port->user_name = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + if (!PG_ARGISNULL(2)) + ssl_in_use = PG_GETARG_BOOL(2); + + port->ssl_in_use = ssl_in_use; + + tupdesc = CreateTemplateTupleDesc(PG_HBA_MATCHES_ATTS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "file_name", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "line_num", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "raw_line", + TEXTOID, -1, 0); + + BlessTupleDesc(tupdesc); + + memset(isnull, 0, sizeof(isnull)); + + /* FIXME rework API to not rely on PostmasterContext */ + ctxt = AllocSetContextCreate(CurrentMemoryContext, "load_hba", + ALLOCSET_DEFAULT_SIZES); + PostmasterContext = AllocSetContextCreate(ctxt, + "Postmaster", + ALLOCSET_DEFAULT_SIZES); + parsed_hba_context = NULL; + if (!load_hba()) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("Invalidation auth configuration file"))); + + check_hba(port); + + if (port->hba->auth_method == uaImplicitReject) + PG_RETURN_NULL(); + + values[0] = CStringGetTextDatum(port->hba->sourcefile); + values[1] = Int32GetDatum(port->hba->linenumber); + values[2] = CStringGetTextDatum(port->hba->rawline); + + MemoryContextDelete(PostmasterContext); + PostmasterContext = NULL; + + return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull)); +} diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0e8a589302..ee77bd1136 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6122,6 +6122,13 @@ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{mapping_number,file_name,line_number,map_name,sys_name,pg_usernamee,error}', prosrc => 'pg_ident_file_mappings' }, +{ oid => '9557', descr => 'show wether the given connection would match an hba line', + proname => 'pg_hba_matches', provolatile => 'v', prorettype => 'record', + proargtypes => 'inet text bool', proisstrict => 'f', + proallargtypes => '{inet,text,bool,text,int4,text}', + proargmodes => '{i,i,i,o,o,o}', + proargnames => '{address,role,ssl,file_name,line_num,raw_line}', + prosrc => 'pg_hba_matches' }, { oid => '1371', descr => 'view system lock information', proname => 'pg_lock_status', prorows => '1000', proretset => 't', provolatile => 'v', prorettype => 'record', proargtypes => '', -- 2.33.1