diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 29738b0..cd9f480 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -7319,6 +7319,11 @@ + pg_hba_rules + summary of client authentication configuration file contents + + + pg_group groups of database users @@ -7857,6 +7862,139 @@ + + <structname>pg_hba_rules</structname> + + + pg_hba_rules + + + + The view pg_hba_rules provides a summary of + the contents of the client authentication configuration file. A row + appears in this view for each entry appearing in the file, with annotations + indicating whether the rule could be applied successfully. + + + + The pg_hba_rules view can be read only by + superusers. + + + + <structname>pg_hba_rules</> Columns + + + + + Name + Type + Description + + + + + line_number + integer + + Line number within client authentication configuration file + the current value was set at + + + + type + text + Type of connection + + + keyword_database + text[] + + List of keyword database names, + name can be all, sameuser, samerole, replication and samegroup + + + + database + text[] + List of database name + + + keyword_user + text[] + + List of keyword user names, + name can be all and a group name prefixed with "+" + + + + user_name + text[] + List of user names + + + keyword_address + text + + List of keyword address names, + name can be all, samehost and samenet + + + + address + inet + Client machine address + + + netmask + inet + Address mask + + + hostname + text + Client host name + + + method + text + Authentication method + + + options + text[] + Configuration options set for authentication method + + + error + text + If not null, an error message indicating why this entry could not be applied + + + +
+ + + If the configuration file contains any errors, error field + indicating the problem. Following is the sample output of the view. + + + +postgres=# select * from pg_hba_rules; + line_number | type | keyword_database | database | keyword_user | user_name | keyword_address | address | netmask | hostname | method | options | error +-------------+-------+------------------+----------+--------------+-----------+-----------------+-----------+-----------------------------------------+----------+--------+---------+------- + 84 | local | {all} | | {all} | | | | | | trust | | + 86 | host | {all} | | {all} | | | 127.0.0.1 | 255.255.255.255 | | trust | | + 88 | host | {all} | | {all} | | | ::1 | ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff | | trust | | +(3 rows) + + + + See for more information about the various + ways to change client authentication configuration. + +
+ <structname>pg_group</structname> diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 960f5b5..798956a 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -54,6 +54,13 @@ database user names and OS user names. + + The system view + pg_hba_rules + can be helpful for pre-testing changes to the client authentication configuration file, or for + diagnosing problems if a SIGHUP signal did not have the desired effects. + + The <filename>pg_hba.conf</filename> File diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index ada2142..3c79a57 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -427,6 +427,12 @@ CREATE VIEW pg_file_settings AS REVOKE ALL on pg_file_settings FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pg_show_all_file_settings() FROM PUBLIC; +CREATE VIEW pg_hba_rules AS + SELECT * FROM pg_hba_rules() AS A; + +REVOKE ALL on pg_hba_rules FROM PUBLIC; +REVOKE EXECUTE ON FUNCTION pg_hba_rules() 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 f1e9a38..8747f0b 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -25,16 +25,23 @@ #include #include +#include "access/htup_details.h" +#include "catalog/objectaddress.h" #include "catalog/pg_collation.h" +#include "catalog/pg_type.h" #include "common/ip.h" +#include "funcapi.h" #include "libpq/ifaddr.h" #include "libpq/libpq.h" +#include "miscadmin.h" #include "postmaster/postmaster.h" #include "regex/regex.h" #include "replication/walsender.h" #include "storage/fd.h" #include "utils/acl.h" +#include "utils/builtins.h" #include "utils/guc.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -75,6 +82,15 @@ typedef struct HbaToken bool quoted; } HbaToken; + +/* Context to use with lookup_hba_line_callback function. */ +typedef struct LookupHbaLineCxt +{ + MemoryContext memcxt; + TupleDesc tupdesc; + Tuplestorestate *tuple_store; +} LookupHbaLineCxt; + /* * pre-parsed content of HBA config file: list of HbaLine structs. * parsed_hba_context is the memory context where it lives. @@ -100,6 +116,10 @@ static List *tokenize_inc_file(List *tokens, const char *outer_filename, const char *inc_filename); static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num); +static Datum getauthmethod(UserAuth auth_method); +static Datum gethba_options(HbaLine *hba); +static void lookup_hba_line_callback(void *context, int lineno, HbaLine *hba, const char *err_msg); +static bool token_is_a_database_keyword(HbaToken *tok); /* * isblank() exists in the ISO C99 spec, but it's not very portable yet, @@ -818,7 +838,7 @@ check_same_host_or_net(SockAddr *raddr, IPCompareMethod method) * NULL. */ static HbaLine * -parse_hba_line(List *line, int line_num, char *raw_line) +parse_hba_line(List *line, int line_num, char *raw_line, char **err_msg, int level) { char *str; struct addrinfo *gai_result; @@ -841,12 +861,13 @@ parse_hba_line(List *line, int line_num, char *raw_line) tokens = lfirst(field); if (tokens->length > 1) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("multiple values specified for connection type"), errhint("Specify exactly one connection type per line."), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("multiple values specified for connection type")); return NULL; } token = linitial(tokens); @@ -855,11 +876,12 @@ parse_hba_line(List *line, int line_num, char *raw_line) #ifdef HAVE_UNIX_SOCKETS parsedline->conntype = ctLocal; #else - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("local connections are not supported by this build"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("local connections are not supported by this build")); return NULL; #endif } @@ -876,21 +898,23 @@ parse_hba_line(List *line, int line_num, char *raw_line) parsedline->conntype = ctHostSSL; else { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("hostssl requires SSL to be turned on"), errhint("Set ssl = on in postgresql.conf."), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("hostssl requires SSL to be turned on")); return NULL; } #else - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("hostssl is not supported by this build"), errhint("Compile with --with-openssl to use SSL connections."), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("hostssl is not supported by this build")); return NULL; #endif } @@ -906,12 +930,13 @@ parse_hba_line(List *line, int line_num, char *raw_line) } /* record type */ else { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("invalid connection type \"%s\"", token->string), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("invalid connection type")); return NULL; } @@ -919,11 +944,12 @@ parse_hba_line(List *line, int line_num, char *raw_line) field = lnext(field); if (!field) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("end-of-line before database specification"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("end-of-line before database specification")); return NULL; } parsedline->databases = NIL; @@ -938,11 +964,12 @@ parse_hba_line(List *line, int line_num, char *raw_line) field = lnext(field); if (!field) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("end-of-line before role specification"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("end-of-line before role specification")); return NULL; } parsedline->roles = NIL; @@ -959,22 +986,24 @@ parse_hba_line(List *line, int line_num, char *raw_line) field = lnext(field); if (!field) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("end-of-line before IP address specification"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("end-of-line before IP address specification")); return NULL; } tokens = lfirst(field); if (tokens->length > 1) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("multiple values specified for host address"), errhint("Specify one address range per line."), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("multiple values specified for host address")); return NULL; } token = linitial(tokens); @@ -1024,7 +1053,7 @@ parse_hba_line(List *line, int line_num, char *raw_line) parsedline->hostname = str; else { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("invalid IP address \"%s\": %s", str, gai_strerror(ret)), @@ -1032,6 +1061,7 @@ parse_hba_line(List *line, int line_num, char *raw_line) line_num, HbaFileName))); if (gai_result) pg_freeaddrinfo_all(hints.ai_family, gai_result); + *err_msg = pstrdup(_("invalid IP address")); return NULL; } @@ -1042,24 +1072,26 @@ parse_hba_line(List *line, int line_num, char *raw_line) { if (parsedline->hostname) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("specifying both host name and CIDR mask is invalid: \"%s\"", token->string), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("specifying both host name and CIDR mask is invalid")); return NULL; } if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1, parsedline->addr.ss_family) < 0) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("invalid CIDR mask in address \"%s\"", token->string), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("invalid CIDR mask in address")); return NULL; } pfree(str); @@ -1071,22 +1103,24 @@ parse_hba_line(List *line, int line_num, char *raw_line) field = lnext(field); if (!field) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("end-of-line before netmask specification"), errhint("Specify an address range in CIDR notation, or provide a separate netmask."), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("end-of-line before netmask specification")); return NULL; } tokens = lfirst(field); if (tokens->length > 1) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("multiple values specified for netmask"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("multiple values specified for netmask")); return NULL; } token = linitial(tokens); @@ -1095,7 +1129,7 @@ parse_hba_line(List *line, int line_num, char *raw_line) &hints, &gai_result); if (ret || !gai_result) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("invalid IP mask \"%s\": %s", token->string, gai_strerror(ret)), @@ -1103,6 +1137,7 @@ parse_hba_line(List *line, int line_num, char *raw_line) line_num, HbaFileName))); if (gai_result) pg_freeaddrinfo_all(hints.ai_family, gai_result); + *err_msg = pstrdup(_("invalid IP mask")); return NULL; } @@ -1112,11 +1147,12 @@ parse_hba_line(List *line, int line_num, char *raw_line) if (parsedline->addr.ss_family != parsedline->mask.ss_family) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("IP address and mask do not match"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("IP address and mask do not match")); return NULL; } } @@ -1127,22 +1163,24 @@ parse_hba_line(List *line, int line_num, char *raw_line) field = lnext(field); if (!field) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("end-of-line before authentication method"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("end-of-line before authentication method")); return NULL; } tokens = lfirst(field); if (tokens->length > 1) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("multiple values specified for authentication type"), errhint("Specify exactly one authentication type per line."), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("multiple values specified for authentication type")); return NULL; } token = linitial(tokens); @@ -1174,11 +1212,12 @@ parse_hba_line(List *line, int line_num, char *raw_line) { if (Db_user_namespace) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("MD5 authentication is not supported when \"db_user_namespace\" is enabled")); return NULL; } parsedline->auth_method = uaMD5; @@ -1211,23 +1250,25 @@ parse_hba_line(List *line, int line_num, char *raw_line) parsedline->auth_method = uaRADIUS; else { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("invalid authentication method \"%s\"", token->string), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("invalid authentication method")); return NULL; } if (unsupauth) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("invalid authentication method \"%s\": not supported by this build", token->string), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("invalid authentication method, not supported by this build")); return NULL; } @@ -1243,22 +1284,24 @@ parse_hba_line(List *line, int line_num, char *raw_line) if (parsedline->conntype == ctLocal && parsedline->auth_method == uaGSS) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("gssapi authentication is not supported on local sockets"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("gssapi authentication is not supported on local sockets")); return NULL; } if (parsedline->conntype != ctLocal && parsedline->auth_method == uaPeer) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("peer authentication is only supported on local sockets"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("peer authentication is only supported on local sockets")); return NULL; } @@ -1271,11 +1314,12 @@ parse_hba_line(List *line, int line_num, char *raw_line) if (parsedline->conntype != ctHostSSL && parsedline->auth_method == uaCert) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("cert authentication is only supported on hostssl connections"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("cert authentication is only supported on hostssl connections")); return NULL; } @@ -1320,11 +1364,12 @@ parse_hba_line(List *line, int line_num, char *raw_line) /* * Got something that's not a name=value pair. */ - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("authentication option not in name=value format: %s", token->string), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("authentication option not in name=value format")); return NULL; } @@ -1357,21 +1402,23 @@ parse_hba_line(List *line, int line_num, char *raw_line) parsedline->ldapbindpasswd || parsedline->ldapsearchattribute) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix")); return NULL; } } else if (!parsedline->ldapbasedn) { - ereport(LOG, + ereport(level, (errcode(ERRCODE_CONFIG_FILE_ERROR), errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + *err_msg = pstrdup(_("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set")); return NULL; } } @@ -1784,7 +1831,7 @@ check_hba(hbaPort *port) * with the old data. */ bool -load_hba(void) +load_hba(hba_line_callback callback, void *callback_context) { FILE *file; List *hba_lines = NIL; @@ -1798,6 +1845,8 @@ load_hba(void) MemoryContext linecxt; MemoryContext oldcxt; MemoryContext hbacxt; + char *err_msg = NULL; + int log_level = LOG; file = AllocateFile(HbaFileName, "r"); if (file == NULL) @@ -1812,18 +1861,24 @@ load_hba(void) linecxt = tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums, &hba_raw_lines); FreeFile(file); + if (callback) + log_level = DEBUG3; + /* Now parse all the lines */ - Assert(PostmasterContext); - hbacxt = AllocSetContextCreate(PostmasterContext, + hbacxt = AllocSetContextCreate(CurrentMemoryContext, "hba parser context", ALLOCSET_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(hbacxt); forthree(line, hba_lines, line_num, hba_line_nums, raw_line, hba_raw_lines) { HbaLine *newline; + int lineno = lfirst_int(line_num); - if ((newline = parse_hba_line(lfirst(line), lfirst_int(line_num), lfirst(raw_line))) == NULL) + if ((newline = parse_hba_line(lfirst(line), lineno, lfirst(raw_line), &err_msg, log_level)) == NULL) { + if (callback) + callback(callback_context, lineno, newline, err_msg); + /* * Parse error in the file, so indicate there's a problem. NB: a * problem in a line will free the memory for all previous lines @@ -1841,10 +1896,24 @@ load_hba(void) continue; } + if (callback) + callback(callback_context, lineno, newline, NULL); new_parsed_lines = lappend(new_parsed_lines, newline); } /* + * If callback function is available, then don't update the saved + * authentication rules. + */ + if (callback) + { + MemoryContextDelete(linecxt); + MemoryContextSwitchTo(oldcxt); + MemoryContextDelete(hbacxt); + return true; + } + + /* * A valid HBA file must have at least one entry; else there's no way to * connect to the postmaster. But only complain about this if we didn't * already have parsing errors. @@ -1870,14 +1939,25 @@ load_hba(void) } /* Loaded new file successfully, replace the one we use */ - if (parsed_hba_context != NULL) - MemoryContextDelete(parsed_hba_context); + discard_hba(); parsed_hba_context = hbacxt; parsed_hba_lines = new_parsed_lines; return true; } +void +discard_hba(void) +{ + if (parsed_hba_context != NULL) + { + MemoryContextDelete(parsed_hba_context); + parsed_hba_context = NULL; + parsed_hba_lines = NIL; + } +} + + /* * Parse one tokenised line from the ident config file and store the result in * an IdentLine structure, or NULL if parsing fails. @@ -2189,8 +2269,7 @@ load_ident(void) FreeFile(file); /* Now parse all the lines */ - Assert(PostmasterContext); - ident_context = AllocSetContextCreate(PostmasterContext, + ident_context = AllocSetContextCreate(CurrentMemoryContext, "ident parser context", ALLOCSET_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(ident_context); @@ -2241,6 +2320,19 @@ load_ident(void) } /* Loaded new file successfully, replace the one we use */ + discard_ident(); + parsed_ident_context = ident_context; + parsed_ident_lines = new_parsed_lines; + + return true; +} + +void +discard_ident(void) +{ + ListCell *parsed_line_cell; + IdentLine *newline; + if (parsed_ident_lines != NIL) { foreach(parsed_line_cell, parsed_ident_lines) @@ -2249,14 +2341,15 @@ load_ident(void) if (newline->ident_user[0] == '/') pg_regfree(&newline->re); } + + parsed_ident_lines = NIL; } + if (parsed_ident_context != NULL) + { MemoryContextDelete(parsed_ident_context); - - parsed_ident_context = ident_context; - parsed_ident_lines = new_parsed_lines; - - return true; + parsed_ident_context = NULL; + } } @@ -2274,3 +2367,582 @@ hba_getauthmethod(hbaPort *port) { check_hba(port); } + +/* + * Returns the Text Datum representation of authentication method + */ +static Datum +getauthmethod(UserAuth auth_method) +{ + Datum result; + + switch (auth_method) + { + case uaReject: + result = CStringGetTextDatum("reject"); + break; + case uaTrust: + result = CStringGetTextDatum("trust"); + break; + case uaIdent: + result = CStringGetTextDatum("ident"); + break; + case uaPassword: + result = CStringGetTextDatum("password"); + break; + case uaMD5: + result = CStringGetTextDatum("md5"); + break; + case uaGSS: + result = CStringGetTextDatum("gss"); + break; + case uaSSPI: + result = CStringGetTextDatum("sspi"); + break; + case uaPAM: + result = CStringGetTextDatum("pam"); + break; + case uaLDAP: + result = CStringGetTextDatum("ldap"); + break; + case uaCert: + result = CStringGetTextDatum("cert"); + break; + case uaRADIUS: + result = CStringGetTextDatum("radius"); + break; + case uaPeer: + result = CStringGetTextDatum("peer"); + break; + default: + elog(ERROR, "unexpected authentication method in parsed HBA entry"); + break; + } + + return result; +} + +/* LDAP supports 10 currently, keep this well above the most any method needs */ +#define MAX_OPTIONS 12 + +static Datum +gethba_options(HbaLine *hba) +{ + StringInfoData str; + int noptions; + Datum options[MAX_OPTIONS]; + char buffer[64]; + + 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) + { + initStringInfo(&str); + appendStringInfoString(&str, "krb_realm="); + appendStringInfoString(&str, hba->krb_realm); + options[noptions++] = CStringGetTextDatum(str.data); + } + } + + if (hba->usermap) + { + initStringInfo(&str); + appendStringInfoString(&str, "map="); + appendStringInfoString(&str, hba->usermap); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->clientcert) + options[noptions++] = CStringGetTextDatum("clientcert=true"); + + if (hba->pamservice) + { + initStringInfo(&str); + appendStringInfoString(&str, "pamservice="); + appendStringInfoString(&str, hba->pamservice); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->auth_method == uaLDAP) + { + if (hba->ldapserver) + { + initStringInfo(&str); + appendStringInfoString(&str, "ldapserver="); + appendStringInfoString(&str, hba->ldapserver); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->ldapport) + { + initStringInfo(&str); + snprintf(buffer, sizeof(buffer), "%d", hba->ldapport); + appendStringInfoString(&str, "ldapport="); + appendStringInfoString(&str, buffer); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->ldaptls) + options[noptions++] = CStringGetTextDatum("ldaptls=true"); + + if (hba->ldapprefix) + { + initStringInfo(&str); + appendStringInfoString(&str, "ldapprefix="); + appendStringInfoString(&str, hba->ldapprefix); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->ldapsuffix) + { + initStringInfo(&str); + appendStringInfoString(&str, "ldapsuffix="); + appendStringInfoString(&str, hba->ldapsuffix); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->ldapbasedn) + { + initStringInfo(&str); + appendStringInfoString(&str, "ldapbasedn="); + appendStringInfoString(&str, hba->ldapbasedn); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->ldapbinddn) + { + initStringInfo(&str); + appendStringInfoString(&str, "ldapbinddn="); + appendStringInfoString(&str, hba->ldapbinddn); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->ldapbindpasswd) + { + initStringInfo(&str); + appendStringInfoString(&str, "ldapbindpasswd="); + appendStringInfoString(&str, hba->ldapbindpasswd); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->ldapsearchattribute) + { + initStringInfo(&str); + appendStringInfoString(&str, "ldapsearchattribute="); + appendStringInfoString(&str, hba->ldapsearchattribute); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->ldapscope) + { + initStringInfo(&str); + snprintf(buffer, sizeof(buffer), "%d", hba->ldapscope); + appendStringInfoString(&str, "ldapscope="); + appendStringInfoString(&str, buffer); + options[noptions++] = CStringGetTextDatum(str.data); + } + } + + if (hba->auth_method == uaRADIUS) + { + if (hba->radiusserver) + { + initStringInfo(&str); + appendStringInfoString(&str, "radiusserver="); + appendStringInfoString(&str, hba->radiusserver); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->radiussecret) + { + initStringInfo(&str); + appendStringInfoString(&str, "radiussecret="); + appendStringInfoString(&str, hba->radiussecret); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->radiusidentifier) + { + initStringInfo(&str); + appendStringInfoString(&str, "radiusidentifier="); + appendStringInfoString(&str, hba->radiusidentifier); + options[noptions++] = CStringGetTextDatum(str.data); + } + + if (hba->radiusport) + { + initStringInfo(&str); + snprintf(buffer, sizeof(buffer), "%d", hba->radiusport); + appendStringInfoString(&str, "radiusport="); + appendStringInfoString(&str, buffer); + options[noptions++] = CStringGetTextDatum(str.data); + } + } + + Assert(noptions <= MAX_OPTIONS); + if (noptions) + return PointerGetDatum( + construct_array(options, noptions, TEXTOID, -1, false, 'i')); + return PointerGetDatum(NULL); +} + +static bool +token_is_a_database_keyword(HbaToken *tok) +{ + if (token_is_keyword(tok, "replication") + || token_is_keyword(tok, "all") + || token_is_keyword(tok, "sameuser") + || token_is_keyword(tok, "samegroup") + || token_is_keyword(tok, "samerole")) + return true; + return false; +} + +#define NUM_PG_HBA_LOOKUP_ATTS 13 + +static void +lookup_hba_line_callback(void *context, int lineno, HbaLine *hba, const char *err_msg) +{ + Datum values[NUM_PG_HBA_LOOKUP_ATTS]; + bool nulls[NUM_PG_HBA_LOOKUP_ATTS]; + ListCell *dbcell; + char buffer[NI_MAXHOST]; + HeapTuple tuple; + int index; + Datum options; + MemoryContext old_cxt; + LookupHbaLineCxt *mycxt; + + mycxt = (LookupHbaLineCxt *) context; + + index = 0; + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + old_cxt = MemoryContextSwitchTo(mycxt->memcxt); + + /* line_number */ + values[index] = Int32GetDatum(lineno); + + if (err_msg) + { + /* type */ + index++; + nulls[index] = true; + + /* keyword_database */ + index++; + nulls[index] = true; + + /* database */ + index++; + nulls[index] = true; + + /* keyword_user */ + index++; + nulls[index] = true; + + /* user */ + index++; + nulls[index] = true; + + /* keyword_address */ + index++; + nulls[index] = true; + + /* address */ + index++; + nulls[index] = true; + + /* netmask */ + index++; + nulls[index] = true; + + /* hostname */ + index++; + nulls[index] = true; + + /* method */ + index++; + nulls[index] = true; + + /* options */ + index++; + nulls[index] = true; + + /* error */ + index++; + values[index] = CStringGetTextDatum(err_msg); + } + else + { + /* type */ + index++; + switch (hba->conntype) + { + case ctLocal: + values[index] = CStringGetTextDatum("local"); + break; + case ctHost: + values[index] = CStringGetTextDatum("host"); + break; + case ctHostSSL: + values[index] = CStringGetTextDatum("hostssl"); + break; + case ctHostNoSSL: + values[index] = CStringGetTextDatum("hostnossl"); + break; + default: + elog(ERROR, "unexpected connection type in parsed HBA entry"); + break; + } + + /* keyword_database and database */ + index++; + if (list_length(hba->databases) != 0) + { + List *names = NULL; + List *keyword_names = NULL; + HbaToken *tok; + + foreach(dbcell, hba->databases) + { + tok = lfirst(dbcell); + if (token_is_a_database_keyword(tok)) + keyword_names = lappend(keyword_names, tok->string); + else + names = lappend(names, tok->string); + } + + /* keyword_database */ + if (keyword_names != NULL) + values[index] = PointerGetDatum(strlist_to_textarray(keyword_names)); + else + nulls[index] = true; + + /* database */ + index++; + if (names != NULL) + values[index] = PointerGetDatum(strlist_to_textarray(names)); + else + nulls[index] = true; + } + else + { + nulls[index] = true; + index++; + nulls[index] = true; + } + + /* keyword_user and user */ + index++; + if (list_length(hba->roles) != 0) + { + List *roles = NULL; + List *keyword_roles = NULL; + HbaToken *tok; + + foreach(dbcell, hba->roles) + { + tok = lfirst(dbcell); + if (token_is_keyword(tok, "all")) + keyword_roles = lappend(keyword_roles, tok->string); + else + roles = lappend(roles, tok->string); + } + + /* keyword_user */ + if (keyword_roles != NULL) + values[index] = PointerGetDatum(strlist_to_textarray(keyword_roles)); + else + nulls[index] = true; + + /* user */ + index++; + if (roles != NULL) + values[index] = PointerGetDatum(strlist_to_textarray(roles)); + else + nulls[index] = true; + } + else + { + nulls[index] = true; + index++; + nulls[index] = true; + } + + /* keyword_address */ + index++; + switch (hba->ip_cmp_method) + { + case ipCmpAll: + values[index] = CStringGetTextDatum("all"); + break; + case ipCmpSameHost: + values[index] = CStringGetTextDatum("samehost"); + break; + case ipCmpSameNet: + values[index] = CStringGetTextDatum("samenet"); + break; + default: + nulls[index] = true; + break; + } + + /* address */ + index++; + if (pg_getnameinfo_all(&hba->addr, sizeof(struct sockaddr_storage), + buffer, sizeof(buffer), + NULL, 0, + NI_NUMERICHOST) == 0) + { + clean_ipv6_addr(hba->addr.ss_family, buffer); + values[index] = DirectFunctionCall1(inet_in, CStringGetDatum(buffer)); + } + else + nulls[index] = true; + + /* netmask */ + index++; + if (pg_getnameinfo_all(&hba->mask, sizeof(struct sockaddr_storage), + buffer, sizeof(buffer), + NULL, 0, + NI_NUMERICHOST) == 0) + { + clean_ipv6_addr(hba->mask.ss_family, buffer); + values[index] = DirectFunctionCall1(inet_in, CStringGetDatum(buffer)); + } + else + nulls[index] = true; + + /* hostname */ + index++; + if (hba->hostname) + values[index] = CStringGetTextDatum(hba->hostname); + else + nulls[index] = true; + + /* method */ + index++; + values[index] = getauthmethod(hba->auth_method); + + /* options */ + index++; + options = gethba_options(hba); + if (options) + values[index] = PointerGetDatum(options); + else + nulls[index] = true; + + /* error */ + index++; + nulls[index] = true; + } + + tuple = heap_form_tuple(mycxt->tupdesc, values, nulls); + tuplestore_puttuple(mycxt->tuple_store, tuple); + + MemoryContextSwitchTo(old_cxt); + return; +} + + +/* + * SQL-accessible SRF to return all the settings from the pg_hba.conf + * file. + */ +Datum +hba_rules(PG_FUNCTION_ARGS) +{ + Tuplestorestate *tuple_store; + TupleDesc tupdesc; + MemoryContext old_cxt; + LookupHbaLineCxt *mycxt; + ReturnSetInfo *rsi; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to view pg_hba.conf settings")))); + + /* + * We must use the Materialize mode to be safe against HBA file reloads + * while the cursor is open. It's also more efficient than having to look + * up our current position in the parsed list every time. + */ + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + + if (!rsi || !IsA(rsi, ReturnSetInfo) || + (rsi->allowedModes & SFRM_Materialize) == 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + rsi->returnMode = SFRM_Materialize; + + /* + * Create the tupledesc and tuplestore in the per_query context as + * required for SFRM_Materialize. + */ + old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory); + + tupdesc = CreateTemplateTupleDesc(NUM_PG_HBA_LOOKUP_ATTS, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "line_number", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "keyword_database", + TEXTARRAYOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "database", + TEXTARRAYOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "keyword_user", + TEXTARRAYOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "user_name", + TEXTARRAYOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "keyword_address", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 8, "address", + INETOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 9, "netmask", + INETOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 10, "hostname", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 11, "method", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 12, "options", + TEXTARRAYOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 13, "error", + TEXTOID, -1, 0); + BlessTupleDesc(tupdesc); + + tuple_store = + tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random, + false, work_mem); + + MemoryContextSwitchTo(old_cxt); + + mycxt = (LookupHbaLineCxt *) palloc(sizeof(LookupHbaLineCxt)); + mycxt->memcxt = AllocSetContextCreate(CurrentMemoryContext, + "pg_hba_lookup tuple cxt", + ALLOCSET_DEFAULT_SIZES); + mycxt->tupdesc = tupdesc; + mycxt->tuple_store = tuple_store; + + if (!load_hba(lookup_hba_line_callback, mycxt)) + ereport(ERROR, + (errmsg("failed to load pg_hba.conf file"), + errhint("more details may be available in the server log."))); + + MemoryContextDelete(mycxt->memcxt); + pfree(mycxt); + + rsi->setDesc = tupdesc; + rsi->setResult = tuple_store; + + PG_RETURN_NULL(); +} diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 24add74..3630f07 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -1251,7 +1251,7 @@ PostmasterMain(int argc, char *argv[]) /* * Load configuration files for client authentication. */ - if (!load_hba()) + if (!load_hba(NULL, NULL)) { /* * It makes no sense to continue if we fail to load the HBA file, @@ -2503,7 +2503,7 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(PgStatPID, SIGHUP); /* Reload authentication config files too */ - if (!load_hba()) + if (!load_hba(NULL, NULL)) ereport(WARNING, (errmsg("pg_hba.conf not reloaded"))); diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 824d5ab..19446bd 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -192,17 +192,6 @@ PerformAuthentication(Port *port) * FIXME: [fork/exec] Ugh. Is there a way around this overhead? */ #ifdef EXEC_BACKEND - - /* - * load_hba() and load_ident() want to work within the PostmasterContext, - * so create that if it doesn't exist (which it won't). We'll delete it - * again later, in PostgresMain. - */ - if (PostmasterContext == NULL) - PostmasterContext = AllocSetContextCreate(TopMemoryContext, - "Postmaster", - ALLOCSET_DEFAULT_SIZES); - if (!load_hba()) { /* @@ -737,6 +726,14 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username, am_superuser = superuser(); } + /* + * We don't need the HBA and ident data going forward, but we can't rely + * on release of PostmasterContext to clean that up, so discard them + * explicitly here. + */ + discard_hba(); + discard_ident(); + /* * If we're trying to shut down, only superusers can connect, and new * replication connections are not allowed. diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 17ec71d..85e5672 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -3066,6 +3066,8 @@ DATA(insert OID = 2084 ( pg_show_all_settings PGNSP PGUID 12 1 1000 0 0 f f f f DESCR("SHOW ALL as a function"); DATA(insert OID = 3329 ( pg_show_all_file_settings PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{25,23,23,25,25,16,25}" "{o,o,o,o,o,o,o}" "{sourcefile,sourceline,seqno,name,setting,applied,error}" _null_ _null_ show_all_file_settings _null_ _null_ _null_ )); DESCR("show config file settings"); +DATA(insert OID = 3343 ( pg_hba_rules PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{23,25,1009,1009,1009,1009,25,869,869,25,25,1009,25}" "{o,o,o,o,o,o,o,o,o,o,o,o,o}" "{line_number,type,keyword_database,database,keyword_user,user_name,keyword_address,address,netmask,hostname,method,options,error}" _null_ _null_ hba_rules _null_ _null_ _null_ )); +DESCR("show pg_hba config rules"); DATA(insert OID = 1371 ( pg_lock_status PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{25,26,26,23,21,25,28,26,26,21,25,23,25,16,16}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath}" _null_ _null_ pg_lock_status _null_ _null_ _null_ )); DESCR("view system lock information"); DATA(insert OID = 2561 ( pg_blocking_pids PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 1007 "23" _null_ _null_ _null_ _null_ _null_ pg_blocking_pids _null_ _null_ _null_ )); diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index dc7d257..970f8c5 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -100,8 +100,20 @@ typedef struct IdentLine /* kluge to avoid including libpq/libpq-be.h here */ typedef struct Port hbaPort; -extern bool load_hba(void); +/* + * Optional callback function type for load_hba() function. + * Currently, valid callback function is passed to load_hba() + * by pg_hba_rules function view to frame the hba tuple. + */ +typedef void (*hba_line_callback) (void *context, int line_num, + HbaLine *hba_line, const char *err_msg); + +extern bool load_hba(hba_line_callback callback, void *callback_context); extern bool load_ident(void); + +extern void discard_hba(void); +extern void discard_ident(void); + extern void hba_getauthmethod(hbaPort *port); extern int check_usermap(const char *usermap_name, const char *pg_role, const char *auth_user, diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 90f5132..93cc6f1 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -1189,6 +1189,9 @@ extern Datum pg_control_recovery(PG_FUNCTION_ARGS); extern Datum row_security_active(PG_FUNCTION_ARGS); extern Datum row_security_active_name(PG_FUNCTION_ARGS); +/* hba.c */ +extern Datum hba_rules(PG_FUNCTION_ARGS); + /* lockfuncs.c */ extern Datum pg_lock_status(PG_FUNCTION_ARGS); extern Datum pg_blocking_pids(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 00700f2..f26f256 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1338,6 +1338,20 @@ 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_rules| SELECT a.line_number, + a.type, + a.keyword_database, + a.database, + a.keyword_user, + a.user_name, + a.keyword_address, + a.address, + a.netmask, + a.hostname, + a.method, + a.options, + a.error + FROM pg_hba_rules() a(line_number, type, keyword_database, database, keyword_user, user_name, keyword_address, address, netmask, hostname, method, options, error); pg_indexes| SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, diff --git a/src/test/regress/input/misc.source b/src/test/regress/input/misc.source index dd2d1b2..3cdd2d7 100644 --- a/src/test/regress/input/misc.source +++ b/src/test/regress/input/misc.source @@ -2,6 +2,9 @@ -- MISC -- +-- catalog view to pg_hba.conf +SELECT * FROM pg_hba_rules; + -- -- BTREE -- diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source index 574ef0d..e286b09 100644 --- a/src/test/regress/output/misc.source +++ b/src/test/regress/output/misc.source @@ -1,6 +1,15 @@ -- -- MISC -- +-- catalog view to pg_hba.conf +SELECT * FROM pg_hba_rules; + line_number | type | keyword_database | database | keyword_user | user_name | keyword_address | address | netmask | hostname | method | options | error +-------------+-------+------------------+----------+--------------+-----------+-----------------+-----------+-----------------------------------------+----------+--------+---------+------- + 84 | local | {all} | | {all} | | | | | | trust | | + 86 | host | {all} | | {all} | | | 127.0.0.1 | 255.255.255.255 | | trust | | + 88 | host | {all} | | {all} | | | ::1 | ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff | | trust | | +(3 rows) + -- -- BTREE -- diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 6c6d519..cbfbe16 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1118,6 +1118,7 @@ LogicalOutputPluginWriterWrite LogicalRewriteMappingData LogicalTape LogicalTapeSet +LookupHbaLineCxt MAGIC MBuf MEMORY_BASIC_INFORMATION