From 865b181ea989bb5c52fad2c3c9e20a38aa173cf3 Mon Sep 17 00:00:00 2001 From: Julien Rouhaud 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