From 8a069abd180250a5863160ed3b510a9d4c21c207 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 2 Nov 2001 18:39:57 +0000 Subject: [PATCH] Fix pg_pwd caching mechanism, which was broken by changes to fork postmaster children before client auth step. Postmaster now rereads pg_pwd on receipt of SIGHUP, the same way that pg_hba.conf is handled. No cycles need be expended to validate password cache validity during connection startup. --- doc/src/sgml/client-auth.sgml | 68 ++++++++++--- doc/src/sgml/runtime.sgml | 8 +- src/backend/commands/user.c | 27 ++---- src/backend/libpq/crypt.c | 184 ++++++++++++++++++------------------ src/backend/postmaster/postmaster.c | 11 ++- src/include/libpq/crypt.h | 27 +++--- 6 files changed, 183 insertions(+), 142 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 19d81f960b..1f1674904a 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1,4 +1,4 @@ - + Client Authentication @@ -68,6 +68,19 @@ + Each record specifies a connection type, a client IP address range + (if relevant for the connection type), a database name or names, + and the authentication method to be used for connections matching + these parameters. + The first record that matches the type, client address and requested + database name of a connection attempt is used to do the + authentication step. There is no fall-through or + backup: if one record is chosen and the authentication + fails, the following records are not considered. If no record + matches, the access will be denied. + + + A record may have one of the three formats local database authentication-method [ authentication-option ] @@ -107,7 +120,9 @@ hostssl database IP-address-l option or equivalent configuration - setting when the server is started. + setting when the server is started. (Note: host + records will match either SSL or non-SSL connection attempts, but + hostssl records match only SSL connections.) @@ -131,8 +146,9 @@ hostssl database IP-addressIP mask - These two fields control to which hosts a - host record applies, based on their IP + These two fields specify to which client machines a + host or hostssl + record applies, based on their IP address. (Of course IP addresses can be spoofed but this consideration is beyond the scope of Postgres.) The precise logic is that @@ -151,7 +167,8 @@ hostssl database IP-address Specifies the method that users must use to authenticate themselves - when connecting to that database. The possible choices follow, + when connecting under the control of this authentication record. + The possible choices are summarized here, details are in . @@ -322,17 +339,27 @@ hostssl database IP-address + - The first record that matches the client IP address and requested - database name of a connection attempt is used to do the - authentication step. There is no fall-through or - backup: if one record is chosen and the authentication - fails, the following records are not considered. If no record - matches, the access will be denied. + + Since the pg_hba.conf records are examined + sequentially for each connection attempt, order of the records is + very significant. Typically, earlier records will have tight + connection match parameters and weaker authentication methods, + while later records will have looser match parameters and stronger + authentication methods. For example, one might wish to use + trust authentication for local TCP connections but + require a password for remote TCP connections. In this case a + record specifying trust authentication for connections + from 127.0.0.1 would appear before a record specifying password + authentication for a wider range of allowed client IP addresses. - The pg_hba.conf file is loaded only on startup + + SIGHUP + + The pg_hba.conf file is read on startup and when the postmaster receives a SIGHUP signal. If you edit the file on an active system, you will need to signal the postmaster @@ -632,7 +659,7 @@ host all 192.168.0.0 255.255.0.0 ident omicron to connect as the database user he is requesting to connect as. This is controlled by the ident map argument that follows the ident keyword in the - pg_hba.conf file. The simplest ident map is + 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. @@ -640,7 +667,8 @@ host all 192.168.0.0 255.255.0.0 ident omicron pg_ident.conf - Ident maps are held in the file pg_ident.conf + Ident maps other than sameuser are defined + in the file pg_ident.conf in the data directory, which contains lines of the general form: map-name ident-username database-username @@ -657,6 +685,18 @@ host all 192.168.0.0 255.255.0.0 ident omicron versa. + + + SIGHUP + + The pg_ident.conf file is read on startup + and when the postmaster receives a + SIGHUP signal. If you edit the file on an + active system, you will need to signal the postmaster + (using pg_ctl reload or kill -HUP) + to make it re-read the file. + + A pg_ident.conf file that could be used in conjunction with the pg_hba.conf file in @@ -479,8 +479,10 @@ syslog = 2 SIGHUP The configuration file is reread whenever the postmaster receives - a SIGHUP signal. This signal is also propagated to all running - backend processes, so that running sessions get the new default. + a SIGHUP signal (which is most easily sent by means + of pg_ctl reload). The postmaster also propagates + this signal to all already-running backend processes, so that + existing sessions also get the new default. Alternatively, you can send the signal to only one backend process directly. diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 758cf365c8..3897a5c75e 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.87 2001/11/01 18:09:58 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.88 2001/11/02 18:39:57 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "access/heapam.h" @@ -33,14 +34,15 @@ #include "utils/syscache.h" -static void CheckPgUserAclNotNull(void); extern bool Password_encryption; +static void CheckPgUserAclNotNull(void); + /*--------------------------------------------------------------------- * write_password_file / update_pg_pwd * * copy the modified contents of pg_shadow to a file used by the postmaster - * for user authentication. The file is stored as $PGDATA/pg_pwd. + * for user authentication. The file is stored as $PGDATA/global/pg_pwd. * * This function set is both a trigger function for direct updates to pg_shadow * as well as being called directly from create/alter/drop user. @@ -57,7 +59,6 @@ write_password_file(Relation rel) *tempname; int bufsize; FILE *fp; - int flagfd; mode_t oumask; HeapScanDesc scan; HeapTuple tuple; @@ -133,7 +134,7 @@ write_password_file(Relation rel) /* * The extra columns we emit here are not really necessary. To remove * them, the parser in backend/libpq/crypt.c would need to be - * adjusted. Initdb might also need adjustments. + * adjusted. */ fprintf(fp, "%s" @@ -168,6 +169,7 @@ write_password_file(Relation rel) /* * Rename the temp file to its final name, deleting the old pg_pwd. + * We expect that rename(2) is an atomic action. */ if (rename(tempname, filename)) elog(ERROR, "rename %s to %s: %m", tempname, filename); @@ -176,19 +178,10 @@ write_password_file(Relation rel) pfree((void *) filename); /* - * Create a flag file the postmaster will detect the next time it - * tries to authenticate a user. The postmaster will know to reload - * the pg_pwd file contents. Note: we used to elog(ERROR) if the file - * creation failed, but it's a little silly to abort the transaction - * at this point, so let's just make it a NOTICE. + * Signal the postmaster to reload its password-file cache. */ - filename = crypt_getpwdreloadfilename(); - flagfd = BasicOpenFile(filename, O_WRONLY | O_CREAT, 0600); - if (flagfd < 0) - elog(NOTICE, "write_password_file: unable to write %s: %m", filename); - else - close(flagfd); - pfree((void *) filename); + if (IsUnderPostmaster) + kill(getppid(), SIGHUP); } diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index 8decc40d0b..83921ee014 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -9,7 +9,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/libpq/crypt.c,v 1.40 2001/11/01 18:10:48 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/libpq/crypt.c,v 1.41 2001/11/02 18:39:57 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,6 +17,9 @@ #include #include +#ifdef HAVE_CRYPT_H +#include +#endif #include "libpq/crypt.h" #include "libpq/libpq.h" @@ -24,15 +27,15 @@ #include "storage/fd.h" #include "utils/nabstime.h" -#ifdef HAVE_CRYPT_H -#include -#endif -char **pwd_cache = NULL; -int pwd_cache_count = 0; +#define CRYPT_PWD_FILE "pg_pwd" + + +static char **pwd_cache = NULL; +static int pwd_cache_count = 0; /* - * crypt_getpwdfilename --- get name of password file + * crypt_getpwdfilename --- get full pathname of password file * * Note that result string is palloc'd, and should be freed by the caller. */ @@ -50,28 +53,8 @@ crypt_getpwdfilename(void) } /* - * crypt_getpwdreloadfilename --- get name of password-reload-needed flag file - * - * Note that result string is palloc'd, and should be freed by the caller. + * Open the password file if possible (return NULL if not) */ -char * -crypt_getpwdreloadfilename(void) -{ - char *pwdfilename; - int bufsize; - char *rpfnam; - - pwdfilename = crypt_getpwdfilename(); - bufsize = strlen(pwdfilename) + strlen(CRYPT_PWD_RELOAD_SUFX) + 1; - rpfnam = (char *) palloc(bufsize); - snprintf(rpfnam, bufsize, "%s%s", pwdfilename, CRYPT_PWD_RELOAD_SUFX); - pfree(pwdfilename); - - return rpfnam; -} - -/*-------------------------------------------------------------------------*/ - static FILE * crypt_openpwdfile(void) { @@ -123,107 +106,128 @@ compar_user(const void *user_a, const void *user_b) return result; } -/*-------------------------------------------------------------------------*/ - -static void -crypt_loadpwdfile(void) +/* + * Load or reload the password-file cache + */ +void +load_password_cache(void) { - char *filename; - int result; FILE *pwd_file; char buffer[1024]; - filename = crypt_getpwdreloadfilename(); - result = unlink(filename); - pfree(filename); - /* - * We want to delete the flag file before reading the contents of the - * pg_pwd file. If result == 0 then the unlink of the reload file was - * successful. This means that a backend performed a COPY of the - * pg_shadow file to pg_pwd. Therefore we must now do a reload. + * If for some reason we fail to open the password file, preserve the + * old cache contents; this seems better than dropping the cache if, + * say, we are temporarily out of filetable slots. */ - if (!pwd_cache || result == 0) + if (!(pwd_file = crypt_openpwdfile())) + return; + + /* free any old data */ + if (pwd_cache) { - /* free the old data only if this is a reload */ - if (pwd_cache) - { - while (pwd_cache_count--) - free((void *) pwd_cache[pwd_cache_count]); - free((void *) pwd_cache); - pwd_cache = NULL; - pwd_cache_count = 0; - } + while (--pwd_cache_count >= 0) + pfree(pwd_cache[pwd_cache_count]); + pfree(pwd_cache); + pwd_cache = NULL; + pwd_cache_count = 0; + } - if (!(pwd_file = crypt_openpwdfile())) - return; + /* + * Read the file and store its lines in current memory context, + * which we expect will be PostmasterContext. That context will + * live as long as we need the cache to live, ie, until just after + * each postmaster child has completed client authentication. + */ + while (fgets(buffer, sizeof(buffer), pwd_file) != NULL) + { + int blen; /* - * Here is where we load the data from pg_pwd. + * We must remove the return char at the end of the string, as + * this will affect the correct parsing of the password entry. */ - while (fgets(buffer, sizeof(buffer), pwd_file) != NULL) - { - /* - * We must remove the return char at the end of the string, as - * this will affect the correct parsing of the password entry. - */ - if (buffer[(result = strlen(buffer) - 1)] == '\n') - buffer[result] = '\0'; + if (buffer[(blen = strlen(buffer) - 1)] == '\n') + buffer[blen] = '\0'; + if (pwd_cache == NULL) pwd_cache = (char **) - realloc((void *) pwd_cache, - sizeof(char *) * (pwd_cache_count + 1)); - pwd_cache[pwd_cache_count++] = strdup(buffer); - } - FreeFile(pwd_file); - - /* - * Now sort the entries in the cache for faster searching later. - */ - qsort((void *) pwd_cache, pwd_cache_count, sizeof(char *), compar_user); + palloc(sizeof(char *) * (pwd_cache_count + 1)); + else + pwd_cache = (char **) + repalloc((void *) pwd_cache, + sizeof(char *) * (pwd_cache_count + 1)); + pwd_cache[pwd_cache_count++] = pstrdup(buffer); } -} -/*-------------------------------------------------------------------------*/ + FreeFile(pwd_file); + + /* + * Now sort the entries in the cache for faster searching later. + */ + qsort((void *) pwd_cache, pwd_cache_count, sizeof(char *), compar_user); +} -static void +/* + * Parse a line of the password file to extract password and valid-until date. + */ +static bool crypt_parsepwdentry(char *buffer, char **pwd, char **valdate) { char *parse = buffer; int count, i; + *pwd = NULL; + *valdate = NULL; + /* * skip to the password field */ for (i = 0; i < 6; i++) - parse += (strcspn(parse, CRYPT_PWD_FILE_SEPSTR) + 1); + { + parse += strcspn(parse, CRYPT_PWD_FILE_SEPSTR); + if (*parse == '\0') + return false; + parse++; + } /* * store a copy of user password to return */ count = strcspn(parse, CRYPT_PWD_FILE_SEPSTR); *pwd = (char *) palloc(count + 1); - strncpy(*pwd, parse, count); + memcpy(*pwd, parse, count); (*pwd)[count] = '\0'; - parse += (count + 1); + parse += count; + if (*parse == '\0') + { + pfree(*pwd); + *pwd = NULL; + return false; + } + parse++; /* * store a copy of the date login becomes invalid */ count = strcspn(parse, CRYPT_PWD_FILE_SEPSTR); *valdate = (char *) palloc(count + 1); - strncpy(*valdate, parse, count); + memcpy(*valdate, parse, count); (*valdate)[count] = '\0'; - parse += (count + 1); -} -/*-------------------------------------------------------------------------*/ + return true; +} -static int +/* + * Lookup a username in the password-file cache, + * return his password and valid-until date. + */ +static bool crypt_getloginfo(const char *user, char **passwd, char **valuntil) { - crypt_loadpwdfile(); + *passwd = NULL; + *valuntil = NULL; if (pwd_cache) { @@ -236,14 +240,12 @@ crypt_getloginfo(const char *user, char **passwd, char **valuntil) compar_user); if (pwd_entry) { - crypt_parsepwdentry(*pwd_entry, passwd, valuntil); - return STATUS_OK; + if (crypt_parsepwdentry(*pwd_entry, passwd, valuntil)) + return true; } } - *passwd = NULL; - *valuntil = NULL; - return STATUS_ERROR; + return false; } /*-------------------------------------------------------------------------*/ @@ -256,7 +258,7 @@ md5_crypt_verify(const Port *port, const char *user, const char *pgpass) *crypt_pwd; int retval = STATUS_ERROR; - if (crypt_getloginfo(user, &passwd, &valuntil) == STATUS_ERROR) + if (!crypt_getloginfo(user, &passwd, &valuntil)) return STATUS_ERROR; if (passwd == NULL || *passwd == '\0') diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 1f232bfc30..2c96486cd0 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -37,7 +37,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.253 2001/10/28 06:25:47 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.254 2001/11/02 18:39:57 tgl Exp $ * * NOTES * @@ -747,6 +747,12 @@ PostmasterMain(int argc, char *argv[]) ExitPostmaster(1); /* + * Load cached files for client authentication. + */ + load_hba_and_ident(); + load_password_cache(); + + /* * We're ready to rock and roll... */ StartupPID = StartupDataBase(); @@ -852,8 +858,6 @@ ServerLoop(void) later; struct timezone tz; - load_hba_and_ident(); - gettimeofday(&now, &tz); nSockets = initMasks(&readmask, &writemask); @@ -925,6 +929,7 @@ ServerLoop(void) got_SIGHUP = false; ProcessConfigFile(PGC_SIGHUP); load_hba_and_ident(); + load_password_cache(); } /* diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h index 39d677e166..76dabc89bd 100644 --- a/src/include/libpq/crypt.h +++ b/src/include/libpq/crypt.h @@ -1,9 +1,13 @@ /*------------------------------------------------------------------------- * * crypt.h - * Interface to hba.c + * Interface to libpq/crypt.c * + * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California * + * $Id: crypt.h,v 1.17 2001/11/02 18:39:57 tgl Exp $ + * *------------------------------------------------------------------------- */ #ifndef PG_CRYPT_H @@ -11,27 +15,22 @@ #include "libpq/libpq-be.h" -#define CRYPT_PWD_FILE "pg_pwd" -#define CRYPT_PWD_FILE_SEPCHAR "'\\t'" #define CRYPT_PWD_FILE_SEPSTR "\t" -#define CRYPT_PWD_RELOAD_SUFX ".reload" -extern char **pwd_cache; -extern int pwd_cache_count; +#define MD5_PASSWD_LEN 35 + +#define isMD5(passwd) (strncmp((passwd),"md5",3) == 0 && \ + strlen(passwd) == MD5_PASSWD_LEN) -extern char *crypt_getpwdfilename(void); -extern char *crypt_getpwdreloadfilename(void); -extern int md5_crypt_verify(const Port *port, const char *user, const char *pgpass); +extern char *crypt_getpwdfilename(void); +extern void load_password_cache(void); +extern int md5_crypt_verify(const Port *port, const char *user, + const char *pgpass); extern bool md5_hash(const void *buff, size_t len, char *hexsum); extern bool CheckMD5Pwd(char *passwd, char *storedpwd, char *seed); extern bool EncryptMD5(const char *passwd, const char *salt, size_t salt_len, char *buf); -#define MD5_PASSWD_LEN 35 - -#define isMD5(passwd) (strncmp((passwd),"md5",3) == 0 && \ - strlen(passwd) == MD5_PASSWD_LEN) - #endif -- 2.11.0