diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 90953c38f3d..673135144d4 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); /* @@ -288,31 +256,31 @@ next_token(char **lineptr, char *buf, int bufsz, } /* - * Construct a palloc'd HbaToken struct, copying the given string. + * Construct a palloc'd AuthToken struct, copying the given string. */ -static HbaToken * -make_hba_token(const char *token, bool quoted) +static AuthToken * +make_auth_token(const char *token, bool quoted) { - HbaToken *hbatoken; + AuthToken *authtoken; int toklen; toklen = strlen(token); /* we copy string into same palloc block as the struct */ - hbatoken = (HbaToken *) palloc(sizeof(HbaToken) + toklen + 1); - hbatoken->string = (char *) hbatoken + sizeof(HbaToken); - hbatoken->quoted = quoted; - memcpy(hbatoken->string, token, toklen + 1); + authtoken = (AuthToken *) palloc(sizeof(AuthToken) + toklen + 1); + authtoken->string = (char *) authtoken + sizeof(AuthToken); + authtoken->quoted = quoted; + memcpy(authtoken->string, token, toklen + 1); - return hbatoken; + return authtoken; } /* - * Copy a HbaToken struct into freshly palloc'd memory. + * Copy a AuthToken struct into freshly palloc'd memory. */ -static HbaToken * -copy_hba_token(HbaToken *in) +static AuthToken * +copy_auth_token(AuthToken *in) { - HbaToken *out = make_hba_token(in->string, in->quoted); + AuthToken *out = make_auth_token(in->string, in->quoted); return out; } @@ -329,7 +297,7 @@ copy_hba_token(HbaToken *in) * may be non-NIL anyway, so *err_msg must be tested to determine whether * there was an error. * - * The result is a List of HbaToken structs, one for each token in the field, + * The result is a List of AuthToken structs, one for each token in the field, * or NIL if we reached EOL. */ static List * @@ -353,7 +321,7 @@ next_field_expand(const char *filename, char **lineptr, tokens = tokenize_inc_file(tokens, filename, buf + 1, elevel, err_msg); else - tokens = lappend(tokens, make_hba_token(buf, initial_quote)); + tokens = lappend(tokens, make_auth_token(buf, initial_quote)); } while (trailing_comma && (*err_msg == NULL)); return tokens; @@ -364,7 +332,7 @@ next_field_expand(const char *filename, char **lineptr, * Expand a file included from another file into an hba "field" * * Opens and tokenises a file included from another HBA config file with @, - * and returns all values found therein as a flat list of HbaTokens. If a + * and returns all values found therein as a flat list of AuthTokens. If a * @-token is found, recursively expand it. The newly read tokens are * appended to "tokens" (so that foo,bar,@baz does what you expect). * All new tokens are allocated in caller's memory context. @@ -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 */ @@ -444,9 +412,9 @@ tokenize_inc_file(List *tokens, foreach(inc_token, inc_tokens) { - HbaToken *token = lfirst(inc_token); + AuthToken *token = lfirst(inc_token); - tokens = lappend(tokens, copy_hba_token(token)); + tokens = lappend(tokens, copy_auth_token(token)); } } } @@ -456,9 +424,11 @@ tokenize_inc_file(List *tokens, } /* - * Tokenize the given file. + * tokenize_auth_file + * 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 the struct definition + * in libpq/hba.h. * * filename: the absolute path to the target file * file: the already-opened target file @@ -466,14 +436,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 +452,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 +521,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); @@ -600,13 +573,13 @@ is_member(Oid userid, const char *role) } /* - * Check HbaToken list for a match to role, allowing group names. + * Check AuthToken list for a match to role, allowing group names. */ static bool check_role(const char *role, Oid roleid, List *tokens) { ListCell *cell; - HbaToken *tok; + AuthToken *tok; foreach(cell, tokens) { @@ -624,13 +597,13 @@ check_role(const char *role, Oid roleid, List *tokens) } /* - * Check to see if db/role combination matches HbaToken list. + * Check to see if db/role combination matches AuthToken list. */ static bool check_db(const char *dbname, const char *role, Oid roleid, List *tokens) { ListCell *cell; - HbaToken *tok; + AuthToken *tok; foreach(cell, tokens) { @@ -962,8 +935,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; @@ -976,7 +949,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) ListCell *field; List *tokens; ListCell *tokencell; - HbaToken *token; + AuthToken *token; HbaLine *parsedline; parsedline = palloc0(sizeof(HbaLine)); @@ -1097,7 +1070,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) foreach(tokencell, tokens) { parsedline->databases = lappend(parsedline->databases, - copy_hba_token(lfirst(tokencell))); + copy_auth_token(lfirst(tokencell))); } /* Get the roles. */ @@ -1117,7 +1090,7 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) foreach(tokencell, tokens) { parsedline->roles = lappend(parsedline->roles, - copy_hba_token(lfirst(tokencell))); + copy_auth_token(lfirst(tokencell))); } if (parsedline->conntype != ctLocal) @@ -2257,7 +2230,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 +2241,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 +2301,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,12 +2316,12 @@ 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; List *tokens; - HbaToken *token; + AuthToken *token; IdentLine *parsedline; Assert(tok_line->fields != NIL); @@ -3026,7 +2607,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 +2618,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 41b486bceff..7c722ea2ce5 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 00000000000..f46cd935a1c --- /dev/null +++ b/src/backend/utils/adt/hbafuncs.c @@ -0,0 +1,428 @@ +/*------------------------------------------------------------------------- + * + * hbafuncs.c + * Support functions for SQL views of authentication files. + * + * 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 *get_hba_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 * +get_hba_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 AuthToken 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) + { + AuthToken *tok = lfirst(lc); + + names = lappend(names, tok->string); + } + values[index++] = PointerGetDatum(strlist_to_textarray(names)); + } + else + nulls[index++] = true; + + /* user */ + if (hba->roles) + { + /* Flatten AuthToken list to string list; see comment above */ + List *roles = NIL; + + foreach(lc, hba->roles) + { + AuthToken *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 = get_hba_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); +} + +/* + * fill_hba_view + * 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); +} + +/* + * pg_hba_file_rules + * + * SQL-accessible set-returning function 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 8d9f3821b12..13ecb329f80 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -132,6 +132,34 @@ typedef struct IdentLine regex_t re; } IdentLine; +/* + * A single string token lexed from an authentication configuration file + * (pg_ident.conf or pg_hba.conf), together with whether the token has + * been quoted. + */ +typedef struct AuthToken +{ + char *string; + bool quoted; +} AuthToken; + +/* + * TokenizedAuthLine represents one line lexed from an authentication + * configuration file. Each item in the "fields" list is a sub-list of + * AuthTokens. 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 AuthTokens */ + 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 +170,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 93d51905086..49688036a7e 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -153,6 +153,7 @@ AttrMissing AttrNumber AttributeOpts AuthRequest +AuthToken AutoPrewarmSharedState AutoVacOpts AutoVacuumShmemStruct @@ -1063,7 +1064,6 @@ HashState HashTapeInfo HashValueFunc HbaLine -HbaToken HeadlineJsonState HeadlineParsedText HeadlineWordEntry @@ -2699,7 +2699,7 @@ ToastTupleContext ToastedAttribute TocEntry TokenAuxData -TokenizedLine +TokenizedAuthLine TrackItem TransInvalidationInfo TransState