diff options
author | Bruce Momjian <bruce@momjian.us> | 2002-04-04 04:25:54 +0000 |
---|---|---|
committer | Bruce Momjian <bruce@momjian.us> | 2002-04-04 04:25:54 +0000 |
commit | 43a3543a4eb412a895df911eba9d8671ded45c54 (patch) | |
tree | 0ff55e96c81086081325b8e41b444915f99114f1 /src/backend/commands/user.c | |
parent | af10378ab05f7979f0051c09f694709edcee8413 (diff) | |
download | postgresql-43a3543a4eb412a895df911eba9d8671ded45c54.tar.gz postgresql-43a3543a4eb412a895df911eba9d8671ded45c54.zip |
Authentication improvements:
A new pg_hba.conf column, USER
Allow specifiction of lists of users separated by commas
Allow group names specified by +
Allow include files containing lists of users specified by @
Allow lists of databases, and database files
Allow samegroup in database column to match group name matching dbname
Removal of secondary password files
Remove pg_passwd utility
Lots of code cleanup in user.c and hba.c
New data/global/pg_pwd format
New data/global/pg_group file
Diffstat (limited to 'src/backend/commands/user.c')
-rw-r--r-- | src/backend/commands/user.c | 369 |
1 files changed, 284 insertions, 85 deletions
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 98ee4308975..9ebd6fa3a2d 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.94 2002/03/26 19:15:48 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.95 2002/04/04 04:25:45 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -15,6 +15,7 @@ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> +#include <errno.h> #include <unistd.h> #include "access/heapam.h" @@ -27,6 +28,7 @@ #include "libpq/crypt.h" #include "miscadmin.h" #include "storage/pmsignal.h" +#include "utils/acl.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -39,8 +41,205 @@ extern bool Password_encryption; static void CheckPgUserAclNotNull(void); -/*--------------------------------------------------------------------- - * write_password_file / update_pg_pwd + + +/* + * fputs_quote + * + * Outputs string in quotes, with double-quotes duplicated. + * We could use quote_ident(), but that expects varlena. + */ +static void fputs_quote(char *str, FILE *fp) +{ + fputc('"', fp); + while (*str) + { + fputc(*str, fp); + if (*str == '"') + fputc('"', fp); + str++; + } + fputc('"', fp); +} + + + +/* + * group_getfilename --- get full pathname of group file + * + * Note that result string is palloc'd, and should be freed by the caller. + */ +char * +group_getfilename(void) +{ + int bufsize; + char *pfnam; + + bufsize = strlen(DataDir) + strlen("/global/") + + strlen(USER_GROUP_FILE) + 1; + pfnam = (char *) palloc(bufsize); + snprintf(pfnam, bufsize, "%s/global/%s", DataDir, USER_GROUP_FILE); + + return pfnam; +} + + + +/* + * Get full pathname of password file. + * Note that result string is palloc'd, and should be freed by the caller. + */ +char * +user_getfilename(void) +{ + int bufsize; + char *pfnam; + + bufsize = strlen(DataDir) + strlen("/global/") + + strlen(PWD_FILE) + 1; + pfnam = (char *) palloc(bufsize); + snprintf(pfnam, bufsize, "%s/global/%s", DataDir, PWD_FILE); + + return pfnam; +} + + + +/* + * write_group_file for trigger update_pg_pwd_and_pg_group + */ +static void +write_group_file(Relation urel, Relation grel) +{ + char *filename, + *tempname; + int bufsize; + FILE *fp; + mode_t oumask; + HeapScanDesc scan; + HeapTuple tuple; + TupleDesc dsc = RelationGetDescr(grel); + + /* + * Create a temporary filename to be renamed later. This prevents the + * backend from clobbering the pg_group file while the postmaster might + * be reading from it. + */ + filename = group_getfilename(); + bufsize = strlen(filename) + 12; + tempname = (char *) palloc(bufsize); + + snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid); + oumask = umask((mode_t) 077); + fp = AllocateFile(tempname, "w"); + umask(oumask); + if (fp == NULL) + elog(ERROR, "write_group_file: unable to write %s: %m", tempname); + + /* read table */ + scan = heap_beginscan(grel, false, SnapshotSelf, 0, NULL); + while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) + { + Datum datum, grolist_datum; + bool isnull; + char *groname; + IdList *grolist_p; + AclId *aidp; + int i, j, + num; + char *usename; + bool first_user = true; + + datum = heap_getattr(tuple, Anum_pg_group_groname, dsc, &isnull); + if (isnull) + continue; /* ignore NULL groupnames */ + groname = (char *) DatumGetName(datum); + + grolist_datum = heap_getattr(tuple, Anum_pg_group_grolist, dsc, &isnull); + /* Ignore NULL group lists */ + if (isnull) + continue; + + grolist_p = DatumGetIdListP(grolist_datum); + /* + * Check for illegal characters in the group name. + */ + i = strcspn(groname, "\n"); + if (groname[i] != '\0') + { + elog(LOG, "Invalid group name '%s'", groname); + continue; + } + + /* be sure the IdList is not toasted */ + /* scan it */ + num = IDLIST_NUM(grolist_p); + aidp = IDLIST_DAT(grolist_p); + for (i = 0; i < num; ++i) + { + tuple = SearchSysCache(SHADOWSYSID, + PointerGetDatum(aidp[i]), + 0, 0, 0); + if (HeapTupleIsValid(tuple)) + { + usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename); + + /* + * Check for illegal characters in the user name. + */ + j = strcspn(usename, "\n"); + if (usename[j] != '\0') + { + elog(LOG, "Invalid user name '%s'", usename); + continue; + } + + /* File format is: + * "dbname" "user1","user2","user3" + * This matches pg_hba.conf. + */ + if (first_user) + { + fputs_quote(groname, fp); + fputs("\t", fp); + } + else + fputs(" ", fp); + + first_user = false; + fputs_quote(usename, fp); + + ReleaseSysCache(tuple); + } + } + if (!first_user) + fputs("\n", fp); + /* if IdList was toasted, free detoasted copy */ + if ((Pointer) grolist_p != DatumGetPointer(grolist_datum)) + pfree(grolist_p); + } + heap_endscan(scan); + + fflush(fp); + if (ferror(fp)) + elog(ERROR, "%s: %m", tempname); + FreeFile(fp); + + /* + * 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); + + pfree((void *) tempname); + pfree((void *) filename); +} + + + +/* + * write_password_file for trigger update_pg_pwd_and_pg_group * * copy the modified contents of pg_shadow to a file used by the postmaster * for user authentication. The file is stored as $PGDATA/global/pg_pwd. @@ -51,10 +250,9 @@ static void CheckPgUserAclNotNull(void); * We raise an error to force transaction rollback if we detect an illegal * username or password --- illegal being defined as values that would * mess up the pg_pwd parser. - *--------------------------------------------------------------------- */ static void -write_password_file(Relation rel) +write_user_file(Relation urel) { char *filename, *tempname; @@ -63,14 +261,14 @@ write_password_file(Relation rel) mode_t oumask; HeapScanDesc scan; HeapTuple tuple; - TupleDesc dsc = RelationGetDescr(rel); + TupleDesc dsc = RelationGetDescr(urel); /* * Create a temporary filename to be renamed later. This prevents the * backend from clobbering the pg_pwd file while the postmaster might * be reading from it. */ - filename = crypt_getpwdfilename(); + filename = user_getfilename(); bufsize = strlen(filename) + 12; tempname = (char *) palloc(bufsize); @@ -82,26 +280,22 @@ write_password_file(Relation rel) elog(ERROR, "write_password_file: unable to write %s: %m", tempname); /* read table */ - scan = heap_beginscan(rel, false, SnapshotSelf, 0, NULL); + scan = heap_beginscan(urel, false, SnapshotSelf, 0, NULL); while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) { - Datum datum_n, - datum_p, - datum_v; - bool null_n, - null_p, - null_v; - char *str_n, - *str_p, - *str_v; + Datum datum; + bool isnull; + char *usename, + *passwd, + *valuntil; int i; - datum_n = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &null_n); - if (null_n) + datum = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &isnull); + if (isnull) continue; /* ignore NULL usernames */ - str_n = DatumGetCString(DirectFunctionCall1(nameout, datum_n)); + usename = (char *) DatumGetName(datum); - datum_p = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &null_p); + datum = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &isnull); /* * It can be argued that people having a null password shouldn't @@ -110,57 +304,40 @@ write_password_file(Relation rel) * assuming an empty password in that case is better, change this * logic to look something like the code for valuntil. */ - if (null_p) - { - pfree(str_n); + if (isnull) continue; - } - str_p = DatumGetCString(DirectFunctionCall1(textout, datum_p)); - datum_v = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &null_v); - if (null_v) - str_v = pstrdup("\\N"); + passwd = DatumGetCString(DirectFunctionCall1(textout, datum)); + + datum = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &isnull); + if (isnull) + valuntil = pstrdup(""); else - str_v = DatumGetCString(DirectFunctionCall1(nabstimeout, datum_v)); + valuntil = DatumGetCString(DirectFunctionCall1(nabstimeout, datum)); /* * Check for illegal characters in the username and password. */ - i = strcspn(str_n, CRYPT_PWD_FILE_SEPSTR "\n"); - if (str_n[i] != '\0') - elog(ERROR, "Invalid user name '%s'", str_n); - i = strcspn(str_p, CRYPT_PWD_FILE_SEPSTR "\n"); - if (str_p[i] != '\0') - elog(ERROR, "Invalid user password '%s'", str_p); + i = strcspn(usename, "\n"); + if (usename[i] != '\0') + elog(ERROR, "Invalid user name '%s'", usename); + i = strcspn(passwd, "\n"); + if (passwd[i] != '\0') + elog(ERROR, "Invalid user password '%s'", passwd); /* * 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. */ - fprintf(fp, - "%s" - CRYPT_PWD_FILE_SEPSTR - "0" - CRYPT_PWD_FILE_SEPSTR - "x" - CRYPT_PWD_FILE_SEPSTR - "x" - CRYPT_PWD_FILE_SEPSTR - "x" - CRYPT_PWD_FILE_SEPSTR - "x" - CRYPT_PWD_FILE_SEPSTR - "%s" - CRYPT_PWD_FILE_SEPSTR - "%s\n", - str_n, - str_p, - str_v); - - pfree(str_n); - pfree(str_p); - pfree(str_v); + fputs_quote(usename, fp); + fputs(" ", fp); + fputs_quote(passwd, fp); + fputs(" ", fp); + fputs_quote(valuntil, fp); + + pfree(passwd); + pfree(valuntil); } heap_endscan(scan); @@ -178,29 +355,33 @@ write_password_file(Relation rel) pfree((void *) tempname); pfree((void *) filename); - - /* - * Signal the postmaster to reload its password-file cache. - */ - SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE); } /* This is the wrapper for triggers. */ Datum -update_pg_pwd(PG_FUNCTION_ARGS) +update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS) { /* * ExclusiveLock ensures no one modifies pg_shadow while we read it, * and that only one backend rewrites the flat file at a time. It's * OK to allow normal reads of pg_shadow in parallel, however. */ - Relation rel = heap_openr(ShadowRelationName, ExclusiveLock); + Relation urel = heap_openr(ShadowRelationName, ExclusiveLock); + Relation grel = heap_openr(GroupRelationName, ExclusiveLock); - write_password_file(rel); + write_user_file(urel); + write_group_file(urel, grel); /* OK to release lock, since we did not modify the relation */ - heap_close(rel, ExclusiveLock); + heap_close(grel, ExclusiveLock); + heap_close(urel, ExclusiveLock); + + /* + * Signal the postmaster to reload its password & group-file cache. + */ + SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE); + return PointerGetDatum(NULL); } @@ -446,14 +627,14 @@ CreateUser(CreateUserStmt *stmt) } /* - * Write the updated pg_shadow data to the flat password file. + * Now we can clean up; but keep lock until commit. */ - write_password_file(pg_shadow_rel); + heap_close(pg_shadow_rel, NoLock); /* - * Now we can clean up; but keep lock until commit. + * Write the updated pg_shadow and pg_group data to the flat file. */ - heap_close(pg_shadow_rel, NoLock); + update_pg_pwd_and_pg_group(NULL); } @@ -680,14 +861,14 @@ AlterUser(AlterUserStmt *stmt) heap_freetuple(new_tuple); /* - * Write the updated pg_shadow data to the flat password file. + * Now we can clean up. */ - write_password_file(pg_shadow_rel); + heap_close(pg_shadow_rel, NoLock); /* - * Now we can clean up. + * Write the updated pg_shadow and pg_group data to the flat file. */ - heap_close(pg_shadow_rel, NoLock); + update_pg_pwd_and_pg_group(NULL); } @@ -733,7 +914,7 @@ AlterUserSet(AlterUserSetStmt *stmt) { Datum datum; bool isnull; - ArrayType *a; + ArrayType *array; repl_null[Anum_pg_shadow_useconfig-1] = ' '; @@ -741,17 +922,17 @@ AlterUserSet(AlterUserSetStmt *stmt) Anum_pg_shadow_useconfig, &isnull); if (valuestr) - a = GUCArrayAdd(isnull + array = GUCArrayAdd(isnull ? NULL : (ArrayType *) pg_detoast_datum((struct varlena *)datum), stmt->variable, valuestr); else - a = GUCArrayDelete(isnull + array = GUCArrayDelete(isnull ? NULL : (ArrayType *) pg_detoast_datum((struct varlena *)datum), stmt->variable); - repl_val[Anum_pg_shadow_useconfig-1] = PointerGetDatum(a); + repl_val[Anum_pg_shadow_useconfig-1] = PointerGetDatum(array); } newtuple = heap_modifytuple(oldtuple, rel, repl_val, repl_null, repl_repl); @@ -846,7 +1027,7 @@ DropUser(DropUserStmt *stmt) datum = heap_getattr(tmp_tuple, Anum_pg_database_datname, pg_dsc, &null); Assert(!null); - dbname = DatumGetCString(DirectFunctionCall1(nameout, datum)); + dbname = (char *) DatumGetName(datum); elog(ERROR, "DROP USER: user \"%s\" owns database \"%s\", cannot be removed%s", user, dbname, (length(stmt->users) > 1) ? " (no users removed)" : ""); @@ -901,14 +1082,14 @@ DropUser(DropUserStmt *stmt) } /* - * Write the updated pg_shadow data to the flat password file. + * Now we can clean up. */ - write_password_file(pg_shadow_rel); + heap_close(pg_shadow_rel, NoLock); /* - * Now we can clean up. + * Write the updated pg_shadow and pg_group data to the flat file. */ - heap_close(pg_shadow_rel, NoLock); + update_pg_pwd_and_pg_group(NULL); } @@ -1111,6 +1292,11 @@ CreateGroup(CreateGroupStmt *stmt) } heap_close(pg_group_rel, NoLock); + + /* + * Write the updated pg_shadow and pg_group data to the flat file. + */ + update_pg_pwd_and_pg_group(NULL); } @@ -1366,7 +1552,15 @@ AlterGroup(AlterGroupStmt *stmt, const char *tag) ReleaseSysCache(group_tuple); + /* + * Write the updated pg_shadow and pg_group data to the flat files. + */ heap_close(pg_group_rel, NoLock); + + /* + * Write the updated pg_shadow and pg_group data to the flat file. + */ + update_pg_pwd_and_pg_group(NULL); } @@ -1419,4 +1613,9 @@ DropGroup(DropGroupStmt *stmt) elog(ERROR, "DROP GROUP: group \"%s\" does not exist", stmt->name); heap_close(pg_group_rel, NoLock); + + /* + * Write the updated pg_shadow and pg_group data to the flat file. + */ + update_pg_pwd_and_pg_group(NULL); } |