From 9872381090cf4cd45748422df67f1c64b31c5ead Mon Sep 17 00:00:00 2001 From: Magnus Hagander Date: Mon, 15 Sep 2008 12:32:57 +0000 Subject: [PATCH] Parse pg_hba.conf in postmaster, instead of once in each backend for each connection. This makes it possible to catch errors in the pg_hba file when it's being reloaded, instead of silently reloading a broken file and failing only when a user tries to connect. This patch also makes the "sameuser" argument to ident authentication optional. --- doc/src/sgml/client-auth.sgml | 18 +- src/backend/libpq/auth.c | 26 +- src/backend/libpq/crypt.c | 8 +- src/backend/libpq/hba.c | 542 +++++++++++++++---------- src/backend/libpq/pg_ident.conf.sample | 3 +- src/backend/postmaster/postmaster.c | 27 +- src/include/libpq/hba.h | 26 +- src/include/libpq/libpq-be.h | 5 +- 8 files changed, 394 insertions(+), 261 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index e5f56e55d9..828b5e2cae 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1,4 +1,4 @@ - + Client Authentication @@ -509,7 +509,7 @@ host all all 127.0.0.1 255.255.255.255 trust # the connection (typically the Unix user name). # # TYPE DATABASE USER CIDR-ADDRESS METHOD -host postgres all 192.168.93.0/24 ident sameuser +host postgres all 192.168.93.0/24 ident # Allow a user from host 192.168.12.10 to connect to database # "postgres" if the user's password is correctly supplied. @@ -839,8 +839,8 @@ local db1,db2,@demodbs all md5 The ident authentication method works by obtaining the client's - operating system user name, then determining the allowed database - user names using a map file that lists the permitted + operating system user name, then optionally determining the allowed + database user names using a map file that lists the permitted corresponding pairs of names. The determination of the client's user name is the security-critical point, and it works differently depending on the connection type. @@ -928,15 +928,13 @@ local db1,db2,@demodbs all md5 allowed to connect as the database user he is requesting to connect as. This is controlled by the ident map argument that follows the ident key word in the pg_hba.conf - file. There is a predefined ident map sameuser, - which allows any operating system user to connect as the database - user of the same name (if the latter exists). Other maps must be - created manually. + file. If an ident map is not specified, the database user will be + checked with the same name as the operating system user. Other maps + must be created manually. - Ident maps other than sameuser are defined in the - ident map file, which by default is named + Ident maps are defined in the ident map file, which by default is named pg_ident.confpg_ident.conf and is stored in the cluster's data directory. (It is possible to place the map file diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 1c50b8e588..f01be3b18f 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.167 2008/08/01 11:41:12 mha Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.168 2008/09/15 12:32:56 mha Exp $ * *------------------------------------------------------------------------- */ @@ -211,7 +211,7 @@ auth_failed(Port *port, int status) if (status == STATUS_EOF) proc_exit(0); - switch (port->auth_method) + switch (port->hba->auth_method) { case uaReject: errstr = gettext_noop("authentication failed for user \"%s\": host rejected"); @@ -279,7 +279,7 @@ ClientAuthentication(Port *port) errmsg("missing or erroneous pg_hba.conf file"), errhint("See server log for details."))); - switch (port->auth_method) + switch (port->hba->auth_method) { case uaReject: @@ -1761,7 +1761,7 @@ ident_unix(int sock, char *ident_user) /* * Determine the username of the initiator of the connection described * by "port". Then look in the usermap file under the usermap - * port->auth_arg and see if that user is equivalent to Postgres user + * port->hba->usermap and see if that user is equivalent to Postgres user * port->user. * * Return STATUS_OK if yes, STATUS_ERROR if no match (or couldn't get info). @@ -1799,7 +1799,7 @@ authident(hbaPort *port) (errmsg("Ident protocol identifies remote user as \"%s\"", ident_user))); - if (check_ident_usermap(port->auth_arg, port->user_name, ident_user)) + if (check_ident_usermap(port->hba->usermap, port->user_name, ident_user)) return STATUS_OK; else return STATUS_ERROR; @@ -1913,8 +1913,8 @@ CheckPAMAuth(Port *port, char *user, char *password) * not allocated */ /* Optionally, one can set the service name in pg_hba.conf */ - if (port->auth_arg && port->auth_arg[0] != '\0') - retval = pam_start(port->auth_arg, "pgsql@", + if (port->hba->auth_arg && port->hba->auth_arg[0] != '\0') + retval = pam_start(port->hba->auth_arg, "pgsql@", &pam_passw_conv, &pamh); else retval = pam_start(PGSQL_PAM_SERVICE, "pgsql@", @@ -2011,7 +2011,7 @@ CheckLDAPAuth(Port *port) int ldapport = LDAP_PORT; char fulluser[NAMEDATALEN + 256 + 1]; - if (!port->auth_arg || port->auth_arg[0] == '\0') + if (!port->hba->auth_arg || port->hba->auth_arg[0] == '\0') { ereport(LOG, (errmsg("LDAP configuration URL not specified"))); @@ -2035,13 +2035,13 @@ CheckLDAPAuth(Port *port) suffix[0] = '\0'; /* ldap, including port number */ - r = sscanf(port->auth_arg, + r = sscanf(port->hba->auth_arg, "ldap://%127[^:]:%d/%127[^;];%127[^;];%127[^\n]", server, &ldapport, basedn, prefix, suffix); if (r < 3) { /* ldaps, including port number */ - r = sscanf(port->auth_arg, + r = sscanf(port->hba->auth_arg, "ldaps://%127[^:]:%d/%127[^;];%127[^;];%127[^\n]", server, &ldapport, basedn, prefix, suffix); if (r >= 3) @@ -2050,14 +2050,14 @@ CheckLDAPAuth(Port *port) if (r < 3) { /* ldap, no port number */ - r = sscanf(port->auth_arg, + r = sscanf(port->hba->auth_arg, "ldap://%127[^/]/%127[^;];%127[^;];%127[^\n]", server, basedn, prefix, suffix); } if (r < 2) { /* ldaps, no port number */ - r = sscanf(port->auth_arg, + r = sscanf(port->hba->auth_arg, "ldaps://%127[^/]/%127[^;];%127[^;];%127[^\n]", server, basedn, prefix, suffix); if (r >= 2) @@ -2067,7 +2067,7 @@ CheckLDAPAuth(Port *port) { ereport(LOG, (errmsg("invalid LDAP URL: \"%s\"", - port->auth_arg))); + port->hba->auth_arg))); return STATUS_ERROR; } diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index 546806994d..ab237ad3b1 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -9,7 +9,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/libpq/crypt.c,v 1.74 2008/01/01 19:45:49 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/crypt.c,v 1.75 2008/09/15 12:32:56 mha Exp $ * *------------------------------------------------------------------------- */ @@ -54,7 +54,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass) return STATUS_ERROR; /* We can't do crypt with MD5 passwords */ - if (isMD5(shadow_pass) && port->auth_method == uaCrypt) + if (isMD5(shadow_pass) && port->hba->auth_method == uaCrypt) { ereport(LOG, (errmsg("cannot use authentication method \"crypt\" because password is MD5-encrypted"))); @@ -65,7 +65,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass) * Compare with the encrypted or plain password depending on the * authentication method being used for this connection. */ - switch (port->auth_method) + switch (port->hba->auth_method) { case uaMD5: crypt_pwd = palloc(MD5_PASSWD_LEN + 1); @@ -155,7 +155,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass) } } - if (port->auth_method == uaMD5) + if (port->hba->auth_method == uaMD5) pfree(crypt_pwd); if (crypt_client_pass != client_pass) pfree(crypt_client_pass); diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index c552a4b254..a511ee293f 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.166 2008/08/01 09:09:49 mha Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.167 2008/09/15 12:32:56 mha Exp $ * *------------------------------------------------------------------------- */ @@ -41,8 +41,11 @@ #define MAX_TOKEN 256 +/* pre-parsed content of HBA config file */ +static List *parsed_hba_lines = NIL; + /* - * These variables hold the pre-parsed contents of the hba and ident + * These variables hold the pre-parsed contents of the ident * configuration files, as well as the flat auth file. * Each is a list of sublists, one sublist for * each (non-empty, non-comment) line of the file. Each sublist's @@ -52,10 +55,6 @@ * one token, since blank lines are not entered in the data structure. */ -/* pre-parsed content of HBA config file and corresponding line #s */ -static List *hba_lines = NIL; -static List *hba_line_nums = NIL; - /* pre-parsed content of ident usermap file and corresponding line #s */ static List *ident_lines = NIL; static List *ident_line_nums = NIL; @@ -566,131 +565,29 @@ check_db(const char *dbname, const char *role, char *param_str) /* - * Scan the rest of a host record (after the mask field) - * and return the interpretation of it as *userauth_p, *auth_arg_p, and - * *error_p. *line_item points to the next token of the line, and is - * advanced over successfully-read tokens. + * Parse one line in the hba config file and store the result in + * a HbaLine structure. */ -static void -parse_hba_auth(ListCell **line_item, UserAuth *userauth_p, - char **auth_arg_p, bool *error_p) +static bool +parse_hba_line(List *line, int line_num, HbaLine *parsedline) { char *token; - - *auth_arg_p = NULL; - - if (!*line_item) - { - *error_p = true; - return; - } - - token = lfirst(*line_item); - if (strcmp(token, "trust") == 0) - *userauth_p = uaTrust; - else if (strcmp(token, "ident") == 0) - *userauth_p = uaIdent; - else if (strcmp(token, "password") == 0) - *userauth_p = uaPassword; - else if (strcmp(token, "krb5") == 0) - *userauth_p = uaKrb5; - else if (strcmp(token, "gss") == 0) - *userauth_p = uaGSS; - else if (strcmp(token, "sspi") == 0) - *userauth_p = uaSSPI; - else if (strcmp(token, "reject") == 0) - *userauth_p = uaReject; - else if (strcmp(token, "md5") == 0) - *userauth_p = uaMD5; - else if (strcmp(token, "crypt") == 0) - *userauth_p = uaCrypt; -#ifdef USE_PAM - else if (strcmp(token, "pam") == 0) - *userauth_p = uaPAM; -#endif -#ifdef USE_LDAP - else if (strcmp(token, "ldap") == 0) - *userauth_p = uaLDAP; -#endif - else - { - *error_p = true; - return; - } - *line_item = lnext(*line_item); - - /* Get the authentication argument token, if any */ - if (*line_item) - { - token = lfirst(*line_item); - *auth_arg_p = pstrdup(token); - *line_item = lnext(*line_item); - /* If there is more on the line, it is an error */ - if (*line_item) - *error_p = true; - } -} - - -/* - * Process one line from the hba config file. - * - * See if it applies to a connection from a host with IP address port->raddr - * to a database named port->database. If so, return *found_p true - * and fill in the auth arguments into the appropriate port fields. - * If not, leave *found_p as it was. If the record has a syntax error, - * return *error_p true, after issuing a message to the log. If no error, - * leave *error_p as it was. - */ -static void -parse_hba(List *line, int line_num, hbaPort *port, - bool *found_p, bool *error_p) -{ - char *token; - char *db; - char *role; struct addrinfo *gai_result; struct addrinfo hints; int ret; - struct sockaddr_storage addr; - struct sockaddr_storage mask; char *cidr_slash; + char *unsupauth; ListCell *line_item; line_item = list_head(line); + + parsedline->linenumber = line_num; + /* Check the record type. */ token = lfirst(line_item); if (strcmp(token, "local") == 0) { - /* Get the database. */ - line_item = lnext(line_item); - if (!line_item) - goto hba_syntax; - db = lfirst(line_item); - - /* Get the role. */ - line_item = lnext(line_item); - if (!line_item) - goto hba_syntax; - role = lfirst(line_item); - - line_item = lnext(line_item); - if (!line_item) - goto hba_syntax; - - /* Read the rest of the line. */ - parse_hba_auth(&line_item, &port->auth_method, - &port->auth_arg, error_p); - if (*error_p) - goto hba_syntax; - - /* Disallow auth methods that always need TCP/IP sockets to work */ - if (port->auth_method == uaKrb5) - goto hba_syntax; - - /* Does not match if connection isn't AF_UNIX */ - if (!IS_AF_UNIX(port->raddr.addr.ss_family)) - return; + parsedline->conntype = ctLocal; } else if (strcmp(token, "host") == 0 || strcmp(token, "hostssl") == 0 @@ -700,14 +597,7 @@ parse_hba(List *line, int line_num, hbaPort *port, if (token[4] == 's') /* "hostssl" */ { #ifdef USE_SSL - /* Record does not match if we are not on an SSL connection */ - if (!port->ssl) - return; - - /* Placeholder to require specific SSL level, perhaps? */ - /* Or a client certificate */ - - /* Since we were on SSL, proceed as with normal 'host' mode */ + parsedline->conntype = ctHostSSL; #else /* We don't accept this keyword at all if no SSL support */ goto hba_syntax; @@ -716,29 +606,37 @@ parse_hba(List *line, int line_num, hbaPort *port, #ifdef USE_SSL else if (token[4] == 'n') /* "hostnossl" */ { - /* Record does not match if we are on an SSL connection */ - if (port->ssl) - return; + parsedline->conntype = ctHostNoSSL; } #endif + else + { + /* "host", or "hostnossl" and SSL support not built in */ + parsedline->conntype = ctHost; + } + } /* record type */ + else + goto hba_syntax; - /* Get the database. */ - line_item = lnext(line_item); - if (!line_item) - goto hba_syntax; - db = lfirst(line_item); + /* Get the database. */ + line_item = lnext(line_item); + if (!line_item) + goto hba_syntax; + parsedline->database = pstrdup(lfirst(line_item)); - /* Get the role. */ - line_item = lnext(line_item); - if (!line_item) - goto hba_syntax; - role = lfirst(line_item); + /* Get the role. */ + line_item = lnext(line_item); + if (!line_item) + goto hba_syntax; + parsedline->role = pstrdup(lfirst(line_item)); + if (parsedline->conntype != ctLocal) + { /* Read the IP address field. (with or without CIDR netmask) */ line_item = lnext(line_item); if (!line_item) goto hba_syntax; - token = lfirst(line_item); + token = pstrdup(lfirst(line_item)); /* Check if it has a CIDR suffix and if so isolate it */ cidr_slash = strchr(token, '/'); @@ -760,9 +658,10 @@ parse_hba(List *line, int line_num, hbaPort *port, { ereport(LOG, (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("invalid IP address \"%s\" in file \"%s\" line %d: %s", - token, HbaFileName, line_num, - gai_strerror(ret)))); + errmsg("invalid IP address \"%s\": %s", + token, gai_strerror(ret)), + errdetail("In file \"%s\", line %d", + HbaFileName, line_num))); if (cidr_slash) *cidr_slash = '/'; if (gai_result) @@ -773,14 +672,14 @@ parse_hba(List *line, int line_num, hbaPort *port, if (cidr_slash) *cidr_slash = '/'; - memcpy(&addr, gai_result->ai_addr, gai_result->ai_addrlen); + memcpy(&parsedline->addr, gai_result->ai_addr, gai_result->ai_addrlen); pg_freeaddrinfo_all(hints.ai_family, gai_result); /* Get the netmask */ if (cidr_slash) { - if (pg_sockaddr_cidr_mask(&mask, cidr_slash + 1, - addr.ss_family) < 0) + if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1, + parsedline->addr.ss_family) < 0) goto hba_syntax; } else @@ -796,18 +695,19 @@ parse_hba(List *line, int line_num, hbaPort *port, { ereport(LOG, (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("invalid IP mask \"%s\" in file \"%s\" line %d: %s", - token, HbaFileName, line_num, - gai_strerror(ret)))); + errmsg("invalid IP mask \"%s\": %s", + token, gai_strerror(ret)), + errdetail("In file \"%s\", line %d", + HbaFileName, line_num))); if (gai_result) pg_freeaddrinfo_all(hints.ai_family, gai_result); goto hba_other_error; } - memcpy(&mask, gai_result->ai_addr, gai_result->ai_addrlen); + memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen); pg_freeaddrinfo_all(hints.ai_family, gai_result); - if (addr.ss_family != mask.ss_family) + if (parsedline->addr.ss_family != parsedline->mask.ss_family) { ereport(LOG, (errcode(ERRCODE_CONFIG_FILE_ERROR), @@ -816,62 +716,133 @@ parse_hba(List *line, int line_num, hbaPort *port, goto hba_other_error; } } + } /* != ctLocal */ - if (addr.ss_family != port->raddr.addr.ss_family) + /* Get the authentication method */ + line_item = lnext(line_item); + if (!line_item) + goto hba_syntax; + token = lfirst(line_item); + + unsupauth = NULL; + if (strcmp(token, "trust") == 0) + parsedline->auth_method = uaTrust; + else if (strcmp(token, "ident") == 0) + parsedline->auth_method = uaIdent; + else if (strcmp(token, "password") == 0) + parsedline->auth_method = uaPassword; + else if (strcmp(token, "krb5") == 0) +#ifdef KRB5 + parsedline->auth_method = uaKrb5; +#else + unsupauth = "krb5"; +#endif + else if (strcmp(token, "gss") == 0) +#ifdef ENABLE_GSS + parsedline->auth_method = uaGSS; +#else + unsupauth = "gss"; +#endif + else if (strcmp(token, "sspi") == 0) +#ifdef ENABLE_SSPI + parsedline->auth_method = uaSSPI; +#else + unsupauth = "sspi"; +#endif + else if (strcmp(token, "reject") == 0) + parsedline->auth_method = uaReject; + else if (strcmp(token, "md5") == 0) + parsedline->auth_method = uaMD5; + else if (strcmp(token, "crypt") == 0) + parsedline->auth_method = uaCrypt; + else if (strcmp(token, "pam") == 0) +#ifdef USE_PAM + parsedline->auth_method = uaPAM; +#else + unsupauth = "pam"; +#endif + else if (strcmp(token, "ldap") == 0) +#ifdef USE_LDAP + parsedline->auth_method = uaLDAP; +#else + unsupauth = "ldap"; +#endif + else + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("invalid authentication method \"%s\"", + token), + errdetail("In file \"%s\" line %d", + HbaFileName, line_num))); + goto hba_other_error; + } + + if (unsupauth) + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("invalid authentication method \"%s\": not supported on this platform", + token), + errdetail("In file \"%s\" line %d", + HbaFileName, line_num))); + goto hba_other_error; + } + + /* Invalid authentication combinations */ + if (parsedline->conntype == ctLocal && + parsedline->auth_method == uaKrb5) + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("krb5 authentication is not supported on local sockets"), + errdetail("In file \"%s\" line %d", + HbaFileName, line_num))); + goto hba_other_error; + } + + /* Get the authentication argument token, if any */ + line_item = lnext(line_item); + if (line_item) + { + token = lfirst(line_item); + parsedline->auth_arg= pstrdup(token); + } + + /* + * Backwards compatible format of ident authentication - support "naked" ident map + * name, as well as "sameuser"/"samerole" + */ + if (parsedline->auth_method == uaIdent) + { + if (parsedline->auth_arg && strlen(parsedline->auth_arg)) { - /* - * Wrong address family. We allow only one case: if the file has - * IPv4 and the port is IPv6, promote the file address to IPv6 and - * try to match that way. - */ -#ifdef HAVE_IPV6 - if (addr.ss_family == AF_INET && - port->raddr.addr.ss_family == AF_INET6) + if (strcmp(parsedline->auth_arg, "sameuser\n") == 0 || + strcmp(parsedline->auth_arg, "samerole\n") == 0) { - pg_promote_v4_to_v6_addr(&addr); - pg_promote_v4_to_v6_mask(&mask); + /* This is now the default */ + pfree(parsedline->auth_arg); + parsedline->auth_arg = NULL; + parsedline->usermap = NULL; } else -#endif /* HAVE_IPV6 */ { - /* Line doesn't match client port, so ignore it. */ - return; + /* Specific ident map specified */ + parsedline->usermap = parsedline->auth_arg; + parsedline->auth_arg = NULL; } } - - /* Ignore line if client port is not in the matching addr range. */ - if (!pg_range_sockaddr(&port->raddr.addr, &addr, &mask)) - return; - - /* Read the rest of the line. */ - line_item = lnext(line_item); - if (!line_item) - goto hba_syntax; - parse_hba_auth(&line_item, &port->auth_method, - &port->auth_arg, error_p); - if (*error_p) - goto hba_syntax; } - else - goto hba_syntax; - - /* Does the entry match database and role? */ - if (!check_db(port->database_name, port->user_name, db)) - return; - if (!check_role(port->user_name, role)) - return; - - /* Success */ - *found_p = true; - return; + + return true; hba_syntax: if (line_item) ereport(LOG, (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("invalid entry in file \"%s\" at line %d, token \"%s\"", - HbaFileName, line_num, - (char *) lfirst(line_item)))); + errmsg("invalid entry in file \"%s\" at line %d, token \"%s\"", + HbaFileName, line_num, + (char *) lfirst(line_item)))); else ereport(LOG, (errcode(ERRCODE_CONFIG_FILE_ERROR), @@ -880,7 +851,7 @@ hba_syntax: /* Come here if suitable message already logged */ hba_other_error: - *error_p = true; + return false; } @@ -891,28 +862,96 @@ hba_other_error: static bool check_hba(hbaPort *port) { - bool found_entry = false; - bool error = false; ListCell *line; - ListCell *line_num; + HbaLine *hba; - forboth(line, hba_lines, line_num, hba_line_nums) + foreach(line, parsed_hba_lines) { - parse_hba(lfirst(line), lfirst_int(line_num), - port, &found_entry, &error); - if (found_entry || error) - break; - } + hba = (HbaLine *) lfirst(line); - if (!error) - { - /* If no matching entry was found, synthesize 'reject' entry. */ - if (!found_entry) - port->auth_method = uaReject; + /* Check connection type */ + if (hba->conntype == ctLocal) + { + if (!IS_AF_UNIX(port->raddr.addr.ss_family)) + continue; + } + else + { + if (IS_AF_UNIX(port->raddr.addr.ss_family)) + continue; + + /* Check SSL state */ +#ifdef USE_SSL + if (port->ssl) + { + /* Connection is SSL, match both "host" and "hostssl" */ + if (hba->conntype == ctHostNoSSL) + continue; + } + else + { + /* Connection is not SSL, match both "host" and "hostnossl" */ + if (hba->conntype == ctHostSSL) + continue; + } +#else + /* No SSL support, so reject "hostssl" lines */ + if (hba->conntype == ctHostSSL) + continue; +#endif + + /* Check IP address */ + if (port->raddr.addr.ss_family == hba->addr.ss_family) + { + if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask)) + continue; + } +#ifdef HAVE_IPV6 + else if (hba->addr.ss_family == AF_INET && + port->raddr.addr.ss_family == AF_INET6) + { + /* + * Wrong address family. We allow only one case: if the file has + * IPv4 and the port is IPv6, promote the file address to IPv6 and + * try to match that way. + */ + struct sockaddr_storage addrcopy, maskcopy; + memcpy(&addrcopy, &hba->addr, sizeof(addrcopy)); + memcpy(&maskcopy, &hba->mask, sizeof(maskcopy)); + pg_promote_v4_to_v6_addr(&addrcopy); + pg_promote_v4_to_v6_mask(&maskcopy); + + if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy)) + continue; + } +#endif /* HAVE_IPV6 */ + else + /* Wrong address family, no IPV6 */ + continue; + } /* != ctLocal */ + + /* Check database and role */ + if (!check_db(port->database_name, port->user_name, hba->database)) + continue; + + if (!check_role(port->user_name, hba->role)) + continue; + + /* Found a record that matched! */ + port->hba = hba; return true; } - else - return false; + + /* If no matching entry was found, synthesize 'reject' entry. */ + hba = palloc0(sizeof(HbaLine)); + hba->auth_method = uaReject; + port->hba = hba; + return true; + + /* XXX: + * Return false only happens if we have a parsing error, which we can + * no longer have (parsing now in postmaster). Consider changing API. + */ } @@ -967,17 +1006,52 @@ load_role(void) } } +/* + * Free the contents of a hba record + */ +static void +free_hba_record(HbaLine *record) +{ + if (record->database) + pfree(record->database); + if (record->role) + pfree(record->role); + if (record->auth_arg) + pfree(record->auth_arg); +} /* - * Read the config file and create a List of Lists of tokens in the file. + * Free all records on the parsed HBA list */ -void +static void +clean_hba_list(List *lines) +{ + ListCell *line; + + foreach(line, lines) + { + HbaLine *parsed = (HbaLine *)lfirst(line); + if (parsed) + free_hba_record(parsed); + } + list_free(lines); +} + +/* + * Read the config file and create a List of HbaLine records for the contents. + * + * The configuration is read into a temporary list, and if any parse error occurs + * the old list is kept in place and false is returned. Only if the whole file + * parses Ok is the list replaced, and the function returns true. + */ +bool load_hba(void) { FILE *file; - - if (hba_lines || hba_line_nums) - free_lines(&hba_lines, &hba_line_nums); + List *hba_lines = NIL; + List *hba_line_nums = NIL; + ListCell *line, *line_num; + List *new_parsed_lines = NIL; file = AllocateFile(HbaFileName, "r"); /* Failure is fatal since with no HBA entries we can do nothing... */ @@ -989,6 +1063,35 @@ load_hba(void) tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums); FreeFile(file); + + /* Now parse all the lines */ + forboth(line, hba_lines, line_num, hba_line_nums) + { + HbaLine *newline; + + newline = palloc0(sizeof(HbaLine)); + + if (!parse_hba_line(lfirst(line), lfirst_int(line_num), newline)) + { + /* Parse error in the file, so bail out */ + free_hba_record(newline); + pfree(newline); + clean_hba_list(new_parsed_lines); + /* Error has already been reported in the parsing function */ + return false; + } + + new_parsed_lines = lappend(new_parsed_lines, newline); + } + + /* Loaded new file successfully, replace the one we use */ + clean_hba_list(parsed_hba_lines); + parsed_hba_lines = new_parsed_lines; + + /* Free the temporary lists */ + free_lines(&hba_lines, &hba_line_nums); + + return true; } /* @@ -1100,7 +1203,8 @@ ident_syntax: * See if the user with ident username "ident_user" is allowed to act * as Postgres user "pgrole" according to usermap "usermap_name". * - * Special case: For usermap "samerole", don't look in the usermap + * Special case: Usermap NULL, equivalent to what was previously called + * "sameuser" or "samerole", don't look in the usermap * file. That's an implied map where "pgrole" must be identical to * "ident_user" in order to be authorized. * @@ -1115,14 +1219,6 @@ check_ident_usermap(const char *usermap_name, error = false; if (usermap_name == NULL || usermap_name[0] == '\0') - { - ereport(LOG, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("cannot use Ident authentication without usermap field"))); - found_entry = false; - } - else if (strcmp(usermap_name, "sameuser\n") == 0 || - strcmp(usermap_name, "samerole\n") == 0) { if (strcmp(pg_role, ident_user) == 0) found_entry = true; diff --git a/src/backend/libpq/pg_ident.conf.sample b/src/backend/libpq/pg_ident.conf.sample index 4019f6fef1..6f0de64910 100644 --- a/src/backend/libpq/pg_ident.conf.sample +++ b/src/backend/libpq/pg_ident.conf.sample @@ -30,7 +30,6 @@ # # No map names are defined in the default configuration. If all ident # user names and PostgreSQL user names are the same, you don't need -# this file. Instead, use the special map name "sameuser" in -# pg_hba.conf. +# this file. # MAPNAME IDENT-USERNAME PG-USERNAME diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 8bc95f1f2f..72b9387767 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -37,7 +37,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.562 2008/08/25 15:11:01 mha Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.563 2008/09/15 12:32:57 mha Exp $ * * NOTES * @@ -888,7 +888,15 @@ PostmasterMain(int argc, char *argv[]) /* * Load configuration files for client authentication. */ - load_hba(); + if (!load_hba()) + { + /* + * It makes no sense continue if we fail to load the HBA file, since + * there is no way to connect to the database in this case. + */ + ereport(FATAL, + (errmsg("could not load pg_hba.conf"))); + } load_ident(); /* @@ -1927,7 +1935,10 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(PgStatPID, SIGHUP); /* Reload authentication config files too */ - load_hba(); + if (!load_hba()) + ereport(WARNING, + (errmsg("pg_hba.conf not reloaded"))); + load_ident(); #ifdef EXEC_BACKEND @@ -3081,7 +3092,15 @@ BackendInitialize(Port *port) ALLOCSET_DEFAULT_MAXSIZE); MemoryContextSwitchTo(PostmasterContext); - load_hba(); + if (!load_hba()) + { + /* + * It makes no sense continue if we fail to load the HBA file, since + * there is no way to connect to the database in this case. + */ + ereport(FATAL, + (errmsg("could not load pg_hba.conf"))); + } load_ident(); load_role(); #endif diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index 1615084662..3af7db9e62 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -4,7 +4,7 @@ * Interface to hba.c * * - * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.48 2008/08/01 09:09:48 mha Exp $ + * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.49 2008/09/15 12:32:57 mha Exp $ * *------------------------------------------------------------------------- */ @@ -12,6 +12,7 @@ #define HBA_H #include "nodes/pg_list.h" +#include "libpq/pqcomm.h" typedef enum UserAuth @@ -33,10 +34,31 @@ typedef enum UserAuth #endif } UserAuth; +typedef enum ConnType +{ + ctLocal, + ctHost, + ctHostSSL, + ctHostNoSSL +} ConnType; + +typedef struct +{ + int linenumber; + ConnType conntype; + char *database; + char *role; + struct sockaddr_storage addr; + struct sockaddr_storage mask; + UserAuth auth_method; + char *usermap; + char *auth_arg; +} HbaLine; + typedef struct Port hbaPort; extern List **get_role_line(const char *role); -extern void load_hba(void); +extern bool load_hba(void); extern void load_ident(void); extern void load_role(void); extern int hba_getauthmethod(hbaPort *port); diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 6927da2fd0..4d5e0039c8 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -11,7 +11,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/libpq/libpq-be.h,v 1.66 2008/04/26 22:47:40 tgl Exp $ + * $PostgreSQL: pgsql/src/include/libpq/libpq-be.h,v 1.67 2008/09/15 12:32:57 mha Exp $ * *------------------------------------------------------------------------- */ @@ -121,8 +121,7 @@ typedef struct Port /* * Information that needs to be held during the authentication cycle. */ - UserAuth auth_method; - char *auth_arg; + HbaLine *hba; char md5Salt[4]; /* Password salt */ char cryptSalt[2]; /* Password salt */