📄 user.c
字号:
/*------------------------------------------------------------------------- * * user.c * Commands for manipulating users and groups. * * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.128 2003/10/02 06:36:37 petere Exp $ * *------------------------------------------------------------------------- */#include "postgres.h"#include <sys/stat.h>#include <fcntl.h>#include <errno.h>#include <unistd.h>#include "access/heapam.h"#include "catalog/catname.h"#include "catalog/indexing.h"#include "catalog/pg_database.h"#include "catalog/pg_group.h"#include "catalog/pg_shadow.h"#include "catalog/pg_type.h"#include "commands/user.h"#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"#include "utils/guc.h"#include "utils/lsyscache.h"#include "utils/syscache.h"#define PWD_FILE "pg_pwd"#define USER_GROUP_FILE "pg_group"extern bool Password_encryption;static bool user_file_update_needed = false;static bool group_file_update_needed = false;static void CheckPgUserAclNotNull(void);static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple, List *members);static IdList *IdListToArray(List *members);static List *IdArrayToList(IdList *oldarray);/* * fputs_quote * * Outputs string in quotes, with double-quotes duplicated. * We could use quote_ident(), but that expects a TEXT argument. */static voidfputs_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: update the flat group file */static voidwrite_group_file(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) ereport(ERROR, (errcode_for_file_access(), errmsg("could not write to temporary file \"%s\": %m", tempname))); /* * Read pg_group and write the file. Note we use SnapshotSelf to * ensure we see all effects of current transaction. (Perhaps could * do a CommandCounterIncrement beforehand, instead?) */ scan = heap_beginscan(grel, SnapshotSelf, 0, NULL); while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { 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); /* ignore NULL groupnames --- shouldn't happen */ if (isnull) continue; groname = NameStr(*DatumGetName(datum)); /* * Check for invalid characters in the group name. */ i = strcspn(groname, "\n"); if (groname[i] != '\0') { ereport(LOG, (errmsg("invalid group name \"%s\"", groname))); continue; } grolist_datum = heap_getattr(tuple, Anum_pg_group_grolist, dsc, &isnull); /* Ignore NULL group lists */ if (isnull) continue; /* be sure the IdList is not toasted */ grolist_p = DatumGetIdListP(grolist_datum); /* scan grolist */ 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') { ereport(LOG, (errmsg("invalid user name \"%s\"", usename))); continue; } /* * File format is: "dbname" "user1" "user2" "user3" */ 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)) ereport(ERROR, (errcode_for_file_access(), errmsg("could not write to temporary file \"%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)) ereport(ERROR, (errcode_for_file_access(), errmsg("could not rename file \"%s\" to \"%s\": %m", tempname, filename))); pfree((void *) tempname); pfree((void *) filename);}/* * write_user_file: update the flat password file */static voidwrite_user_file(Relation urel){ char *filename, *tempname; int bufsize; FILE *fp; mode_t oumask; HeapScanDesc scan; HeapTuple tuple; 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 = user_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) ereport(ERROR, (errcode_for_file_access(), errmsg("could not write to temporary file \"%s\": %m", tempname))); /* * Read pg_shadow and write the file. Note we use SnapshotSelf to * ensure we see all effects of current transaction. (Perhaps could * do a CommandCounterIncrement beforehand, instead?) */ scan = heap_beginscan(urel, SnapshotSelf, 0, NULL); while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { Datum datum; bool isnull; char *usename, *passwd, *valuntil; int i; datum = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &isnull); /* ignore NULL usernames (shouldn't happen) */ if (isnull) continue; usename = NameStr(*DatumGetName(datum)); datum = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &isnull); /* * It can be argued that people having a null password shouldn't * be allowed to connect under password authentication, because * they need to have a password set up first. If you think * assuming an empty password in that case is better, change this * logic to look something like the code for valuntil. */ if (isnull) continue; passwd = DatumGetCString(DirectFunctionCall1(textout, datum)); datum = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &isnull); if (isnull) valuntil = pstrdup(""); else valuntil = DatumGetCString(DirectFunctionCall1(abstimeout, datum)); /* * Check for illegal characters in the username and password. */ i = strcspn(usename, "\n"); if (usename[i] != '\0') { ereport(LOG, (errmsg("invalid user name \"%s\"", usename))); continue; } i = strcspn(passwd, "\n"); if (passwd[i] != '\0') { ereport(LOG, (errmsg("invalid user password \"%s\"", passwd))); continue; } /* * 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. */ fputs_quote(usename, fp); fputs(" ", fp); fputs_quote(passwd, fp); fputs(" ", fp); fputs_quote(valuntil, fp); fputs("\n", fp); pfree(passwd); pfree(valuntil); } heap_endscan(scan); fflush(fp); if (ferror(fp)) ereport(ERROR, (errcode_for_file_access(), errmsg("could not write to temporary file \"%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)) ereport(ERROR, (errcode_for_file_access(), errmsg("could not rename file \"%s\" to \"%s\": %m", tempname, filename))); pfree((void *) tempname); pfree((void *) filename);}/* * This trigger is fired whenever someone modifies pg_shadow or pg_group * via general-purpose INSERT/UPDATE/DELETE commands. * * XXX should probably have two separate triggers. */Datumupdate_pg_pwd_and_pg_group(PG_FUNCTION_ARGS){ user_file_update_needed = true; group_file_update_needed = true; return PointerGetDatum(NULL);}/* * This routine is called during transaction commit or abort. * * On commit, if we've written pg_shadow or pg_group during the current * transaction, update the flat files and signal the postmaster. * * On abort, just reset the static flags so we don't try to do it on the * next successful commit. * * NB: this should be the last step before actual transaction commit. * If any error aborts the transaction after we run this code, the postmaster * will still have received and cached the changed data; so minimize the * window for such problems. */voidAtEOXact_UpdatePasswordFile(bool isCommit){ Relation urel = NULL; Relation grel = NULL; if (!(user_file_update_needed || group_file_update_needed)) return; if (!isCommit) { user_file_update_needed = false; group_file_update_needed = false; return; } /* * We use ExclusiveLock to ensure that only one backend writes the * flat file(s) at a time. That's sufficient because it's okay to * allow plain reads of the tables in parallel. There is some chance * of a deadlock here (if we were triggered by a user update of * pg_shadow or pg_group, which likely won't have gotten a strong * enough lock), so get the locks we need before writing anything. */ if (user_file_update_needed) urel = heap_openr(ShadowRelationName, ExclusiveLock); if (group_file_update_needed) grel = heap_openr(GroupRelationName, ExclusiveLock); /* Okay to write the files */ if (user_file_update_needed) { user_file_update_needed = false; write_user_file(urel); heap_close(urel, NoLock); } if (group_file_update_needed) { group_file_update_needed = false; write_group_file(grel); heap_close(grel, NoLock); } /* * Signal the postmaster to reload its password & group-file cache. */ SendPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE);}/* * CREATE USER */voidCreateUser(CreateUserStmt *stmt){ Relation pg_shadow_rel; TupleDesc pg_shadow_dsc; HeapScanDesc scan; HeapTuple tuple; Datum new_record[Natts_pg_shadow]; char new_record_nulls[Natts_pg_shadow]; bool user_exists = false, sysid_exists = false, havesysid = false; int max_id; List *item, *option; char *password = NULL; /* PostgreSQL user password */ bool encrypt_password = Password_encryption; /* encrypt password? */ char encrypted_password[MD5_PASSWD_LEN + 1]; int sysid = 0; /* PgSQL system id (valid if havesysid) */ bool createdb = false; /* Can the user create databases? */ bool createuser = false; /* Can this user create users? */ List *groupElts = NIL; /* The groups the user is a member of */ char *validUntil = NULL; /* The time the login is valid * until */ DefElem *dpassword = NULL; DefElem *dsysid = NULL; DefElem *dcreatedb = NULL; DefElem *dcreateuser = NULL; DefElem *dgroupElts = NULL; DefElem *dvalidUntil = NULL; /* Extract options from the statement node tree */ foreach(option, stmt->options) { DefElem *defel = (DefElem *) lfirst(option); if (strcmp(defel->defname, "password") == 0 || strcmp(defel->defname, "encryptedPassword") == 0 || strcmp(defel->defname, "unencryptedPassword") == 0) { if (dpassword) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); dpassword = defel; if (strcmp(defel->defname, "encryptedPassword") == 0) encrypt_password = true; else if (strcmp(defel->defname, "unencryptedPassword") == 0) encrypt_password = false; } else if (strcmp(defel->defname, "sysid") == 0) { if (dsysid) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); dsysid = defel; } else if (strcmp(defel->defname, "createdb") == 0) { if (dcreatedb) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); dcreatedb = defel; } else if (strcmp(defel->defname, "createuser") == 0) { if (dcreateuser) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); dcreateuser = defel; } else if (strcmp(defel->defname, "groupElts") == 0) { if (dgroupElts) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); dgroupElts = defel; } else if (strcmp(defel->defname, "validUntil") == 0) { if (dvalidUntil) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); dvalidUntil = defel; } else elog(ERROR, "option \"%s\" not recognized", defel->defname); } if (dcreatedb) createdb = intVal(dcreatedb->arg) != 0; if (dcreateuser) createuser = intVal(dcreateuser->arg) != 0; if (dsysid) { sysid = intVal(dsysid->arg); if (sysid <= 0)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -