diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c new file mode 100644 index 0000000000..b08872856d --- /dev/null +++ b/src/backend/commands/user.c @@ -0,0 +1,379 @@ +/*------------------------------------------------------------------------- + * + * user.c-- + * use pg_eval to create a new user in the catalog + * + * Copyright (c) 1994, Regents of the University of California + * + * + * + *------------------------------------------------------------------------- + */ +#include /* for sprintf() */ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*--------------------------------------------------------------------- + * UpdatePgPwdFile + * + * copy the modified contents of pg_user to a file used by the postmaster + * for user authentication. The file is stored as $PGDATA/pg_pwd. + *--------------------------------------------------------------------- + */ +static +void UpdatePgPwdFile(char* sql) { + + char* filename; + + filename = crypt_getpwdfilename(); + sprintf(sql, "copy %s to '%s' using delimiters '#'", UserRelationName, filename); + pg_eval(sql, (char**)NULL, (Oid*)NULL, 0); +} + +/*--------------------------------------------------------------------- + * DefineUser + * + * Add the user to the pg_user relation, and if specified make sure the + * user is specified in the desired groups of defined in pg_group. + *--------------------------------------------------------------------- + */ +void DefineUser(CreateUserStmt *stmt) { + + char* pg_user; + Relation pg_user_rel; + TupleDesc pg_user_dsc; + HeapScanDesc scan; + HeapTuple tuple; + Datum datum; + Buffer buffer; + char sql[512]; + char* sql_end; + bool exists = false, + n, + inblock; + int max_id = -1; + + if (!(inblock = IsTransactionBlock())) + BeginTransactionBlock(); + + /* Make sure the user attempting to create a user can insert into the pg_user + * relation. + */ + pg_user = GetPgUserName(); + if (pg_aclcheck(UserRelationName, pg_user, ACL_RD | ACL_WR | ACL_AP) != ACLCHECK_OK) { + UserAbortTransactionBlock(); + elog(WARN, "defineUser: user \"%s\" does not have SELECT and INSERT privilege for \"%s\"", + pg_user, UserRelationName); + return; + } + + /* Scan the pg_user relation to be certain the user doesn't already exist. + */ + pg_user_rel = heap_openr(UserRelationName); + pg_user_dsc = RelationGetTupleDescriptor(pg_user_rel); + /* Secure a write lock on pg_user so we can be sure of what the next usesysid + * should be. + */ + RelationSetLockForWrite(pg_user_rel); + + scan = heap_beginscan(pg_user_rel, false, false, 0, NULL); + while (HeapTupleIsValid(tuple = heap_getnext(scan, 0, &buffer))) { + datum = heap_getattr(tuple, buffer, Anum_pg_user_usename, pg_user_dsc, &n); + + if (!exists && !strncmp((char*)datum, stmt->user, strlen(stmt->user))) + exists = true; + + datum = heap_getattr(tuple, buffer, Anum_pg_user_usesysid, pg_user_dsc, &n); + if ((int)datum > max_id) + max_id = (int)datum; + + ReleaseBuffer(buffer); + } + heap_endscan(scan); + + if (exists) { + RelationUnsetLockForWrite(pg_user_rel); + heap_close(pg_user_rel); + UserAbortTransactionBlock(); + elog(WARN, "defineUser: user \"%s\" has already been created", stmt->user); + return; + } + + /* Build the insert statment to be executed. + */ + sprintf(sql, "insert into %s(usename,usesysid,usecreatedb,usetrace,usesuper,usecatupd,passwd", UserRelationName); +/* if (stmt->password) + strcat(sql, ",passwd"); -- removed so that insert empty string when no password */ + if (stmt->validUntil) + strcat(sql, ",valuntil"); + + sql_end = sql + strlen(sql); + sprintf(sql_end, ") values('%s',%d", stmt->user, max_id + 1); + if (stmt->createdb && *stmt->createdb) + strcat(sql_end, ",'t','t'"); + else + strcat(sql_end, ",'f','t'"); + if (stmt->createuser && *stmt->createuser) + strcat(sql_end, ",'t','t'"); + else + strcat(sql_end, ",'f','t'"); + sql_end += strlen(sql_end); + if (stmt->password) { + sprintf(sql_end, ",'%s'", stmt->password); + sql_end += strlen(sql_end); + } else { + strcpy(sql_end, ",''"); + sql_end += strlen(sql_end); + } + if (stmt->validUntil) { + sprintf(sql_end, ",'%s'", stmt->validUntil); + sql_end += strlen(sql_end); + } + strcat(sql_end, ")"); + + pg_eval(sql, (char**)NULL, (Oid*)NULL, 0); + + /* Add the stuff here for groups. + */ + + RelationUnsetLockForWrite(pg_user_rel); + heap_close(pg_user_rel); + + UpdatePgPwdFile(sql); + + if (IsTransactionBlock() && !inblock) + EndTransactionBlock(); +} + + +extern void AlterUser(AlterUserStmt *stmt) { + + char* pg_user; + Relation pg_user_rel; + TupleDesc pg_user_dsc; + HeapScanDesc scan; + HeapTuple tuple; + Datum datum; + Buffer buffer; + char sql[512]; + char* sql_end; + bool exists = false, + n, + inblock; + int max_id = -1; + + if (!(inblock = IsTransactionBlock())) + BeginTransactionBlock(); + + /* Make sure the user attempting to create a user can insert into the pg_user + * relation. + */ + pg_user = GetPgUserName(); + if (pg_aclcheck(UserRelationName, pg_user, ACL_RD | ACL_WR) != ACLCHECK_OK) { + UserAbortTransactionBlock(); + elog(WARN, "alterUser: user \"%s\" does not have SELECT and UPDATE privilege for \"%s\"", + pg_user, UserRelationName); + return; + } + + /* Scan the pg_user relation to be certain the user exists. + */ + pg_user_rel = heap_openr(UserRelationName); + pg_user_dsc = RelationGetTupleDescriptor(pg_user_rel); + + scan = heap_beginscan(pg_user_rel, false, false, 0, NULL); + while (HeapTupleIsValid(tuple = heap_getnext(scan, 0, &buffer))) { + datum = heap_getattr(tuple, buffer, Anum_pg_user_usename, pg_user_dsc, &n); + + if (!strncmp((char*)datum, stmt->user, strlen(stmt->user))) { + exists = true; + ReleaseBuffer(buffer); + break; + } + } + heap_endscan(scan); + heap_close(pg_user_rel); + + if (!exists) { + UserAbortTransactionBlock(); + elog(WARN, "alterUser: user \"%s\" does not exist", stmt->user); + return; + } + + /* Create the update statement to modify the user. + */ + sprintf(sql, "update %s set", UserRelationName); + sql_end = sql; + if (stmt->password) { + sql_end += strlen(sql_end); + sprintf(sql_end, " passwd = '%s'", stmt->password); + } + if (stmt->createdb) { + if (sql_end != sql) + strcat(sql_end, ","); + sql_end += strlen(sql_end); + if (*stmt->createdb) + strcat(sql_end, " usecreatedb = 't'"); + else + strcat(sql_end, " usecreatedb = 'f'"); + } + if (stmt->createuser) { + if (sql_end != sql) + strcat(sql_end, ","); + sql_end += strlen(sql_end); + if (*stmt->createuser) + strcat(sql_end, " usesuper = 't'"); + else + strcat(sql_end, " usesuper = 'f'"); + } + if (stmt->validUntil) { + if (sql_end != sql) + strcat(sql_end, ","); + sql_end += strlen(sql_end); + sprintf(sql_end, " valuntil = '%s'", stmt->validUntil); + } + if (sql_end != sql) { + sql_end += strlen(sql_end); + sprintf(sql_end, " where usename = '%s'", stmt->user); + pg_eval(sql, (char**)NULL, (Oid*)NULL, 0); + } + + /* do the pg_group stuff here */ + + UpdatePgPwdFile(sql); + + if (IsTransactionBlock() && !inblock) + EndTransactionBlock(); +} + + +extern void RemoveUser(char* user) { + + char* pg_user; + Relation pg_rel; + TupleDesc pg_dsc; + HeapScanDesc scan; + HeapTuple tuple; + Datum datum; + Buffer buffer; + char sql[256]; + bool n, + inblock; + int usesysid = -1, + ndbase = 0; + char** dbase = NULL; + + if (!(inblock = IsTransactionBlock())) + BeginTransactionBlock(); + + /* Make sure the user attempting to create a user can delete from the pg_user + * relation. + */ + pg_user = GetPgUserName(); + if (pg_aclcheck(UserRelationName, pg_user, ACL_RD | ACL_WR) != ACLCHECK_OK) { + UserAbortTransactionBlock(); + elog(WARN, "removeUser: user \"%s\" does not have SELECT and DELETE privilege for \"%s\"", + pg_user, UserRelationName); + return; + } + + /* Perform a scan of the pg_user relation to find the usesysid of the user to + * be deleted. If it is not found, then return a warning message. + */ + pg_rel = heap_openr(UserRelationName); + pg_dsc = RelationGetTupleDescriptor(pg_rel); + + scan = heap_beginscan(pg_rel, false, false, 0, NULL); + while (HeapTupleIsValid(tuple = heap_getnext(scan, 0, &buffer))) { + datum = heap_getattr(tuple, buffer, Anum_pg_user_usename, pg_dsc, &n); + + if (!strncmp((char*)datum, user, strlen(user))) { + usesysid = (int)heap_getattr(tuple, buffer, Anum_pg_user_usesysid, pg_dsc, &n); + ReleaseBuffer(buffer); + break; + } + ReleaseBuffer(buffer); + } + heap_endscan(scan); + heap_close(pg_rel); + + if (usesysid == -1) { + UserAbortTransactionBlock(); + elog(WARN, "removeUser: user \"%s\" does not exist", user); + return; + } + + /* Perform a scan of the pg_database relation to find the databases owned by + * usesysid. Then drop them. + */ + pg_rel = heap_openr(DatabaseRelationName); + pg_dsc = RelationGetTupleDescriptor(pg_rel); + + scan = heap_beginscan(pg_rel, false, false, 0, NULL); + while (HeapTupleIsValid(tuple = heap_getnext(scan, 0, &buffer))) { + datum = heap_getattr(tuple, buffer, Anum_pg_database_datdba, pg_dsc, &n); + + if ((int)datum == usesysid) { + datum = heap_getattr(tuple, buffer, Anum_pg_database_datname, pg_dsc, &n); + if (memcmp((void*)datum, "template1", 9)) { + dbase = (char**)repalloc((void*)dbase, sizeof(char*) * (ndbase + 1)); + dbase[ndbase] = (char*)palloc(NAMEDATALEN + 1); + memcpy((void*)dbase[ndbase], (void*)datum, NAMEDATALEN); + dbase[ndbase++][NAMEDATALEN] = '\0'; + } + } + ReleaseBuffer(buffer); + } + heap_endscan(scan); + heap_close(pg_rel); + + while (ndbase--) { + elog(NOTICE, "Dropping database %s", dbase[ndbase]); + sprintf(sql, "drop database %s", dbase[ndbase]); + pfree((void*)dbase[ndbase]); + pg_eval(sql, (char**)NULL, (Oid*)NULL, 0); + } + if (dbase) + pfree((void*)dbase); + + /* Since pg_user is global over all databases, one of two things must be done + * to insure complete consistency. First, pg_user could be made non-global. + * This would elminate the code above for deleting database and would require + * the addition of code to delete tables, views, etc owned by the user. + * + * The second option would be to create a means of deleting tables, view, + * etc. owned by the user from other databases. Pg_user is global and so + * this must be done at some point. + * + * Let us not forget that the user should be removed from the pg_groups also. + * + * Todd A. Brandys 11/18/1997 + * + */ + + /* Remove the user from the pg_user table + */ + sprintf(sql, "delete from %s where usename = '%s'", UserRelationName, user); + pg_eval(sql, (char**)NULL, (Oid*)NULL, 0); + + UpdatePgPwdFile(sql); + + if (IsTransactionBlock() && !inblock) + EndTransactionBlock(); +} diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c new file mode 100644 index 0000000000..c75d8ac459 --- /dev/null +++ b/src/backend/libpq/crypt.c @@ -0,0 +1,182 @@ +/*------------------------------------------------------------------------- + * + * crypt.c-- + * Look into pg_user and check the encrypted password with the one + * passed in from the frontend. + * + * + *------------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#ifdef HAVE_CRYPT_H +#include +#endif + +#include +#include +#include + +char* crypt_getpwdfilename() { + + static char* filename = NULL; + + if (!filename) { + char* env; + + env = getenv("PGDATA"); + filename = (char*)malloc(strlen(env) + strlen(CRYPT_PWD_FILE) + 2); + sprintf(filename, "%s/%s", env, CRYPT_PWD_FILE); + } + + return filename; +} + +/*-------------------------------------------------------------------------*/ + +static +FILE* crypt_openpwdfile() { + + char* filename; + + filename = crypt_getpwdfilename(); + return (fopen(filename, "r")); +} + +/*-------------------------------------------------------------------------*/ + +static +void crypt_parsepwdfile(FILE* datafile, char** login, char** pwd, char** valdate) { + + char buffer[256]; + char* parse; + int count, + i; + + fgets(buffer, 256, datafile); + parse = buffer; + + /* store a copy of user login to return + */ + count = strcspn(parse, "#"); + *login = (char*)malloc(count + 1); + strncpy(*login, parse, count); + (*login)[count] = '\0'; + parse += (count + 1); + + /* skip to the password field + */ + for (i = 0; i < 5; i++) + parse += (strcspn(parse, "#") + 1); + + /* store a copy of user password to return + */ + count = strcspn(parse, "#"); + *pwd = (char*)malloc(count + 1); + strncpy(*pwd, parse, count); + (*pwd)[count] = '\0'; + parse += (count + 1); + + /* store a copy of date login becomes invalid + */ + count = strcspn(parse, "#"); + *valdate = (char*)malloc(count + 1); + strncpy(*valdate, parse, count); + (*valdate)[count] = '\0'; + parse += (count + 1); +} + +/*-------------------------------------------------------------------------*/ + +static +void crypt_getloginfo(const char* user, char** passwd, char** valuntil) { + + FILE* datafile; + char* login; + char* pwd; + char* valdate; + + *passwd = NULL; + *valuntil = NULL; + + if (!(datafile = crypt_openpwdfile())) + return; + + while (!feof(datafile)) { + crypt_parsepwdfile(datafile, &login, &pwd, &valdate); + if (!strcmp(login, user)) { + free((void*)login); + *passwd = pwd; + *valuntil = valdate; + fclose(datafile); + return; + } + free((void*)login); + free((void*)pwd); + free((void*)valdate); + } + fclose(datafile); +} + +/*-------------------------------------------------------------------------*/ + +MsgType crypt_salt(const char* user) { + + char* passwd; + char* valuntil; + + crypt_getloginfo(user, &passwd, &valuntil); + + if (passwd == NULL || *passwd == '\0') { + if (passwd) free((void*)passwd); + if (valuntil) free((void*)valuntil); + return STARTUP_UNSALT_MSG; + } + + free((void*)passwd); + if (valuntil) free((void*)valuntil); + return STARTUP_SALT_MSG; +} + +/*-------------------------------------------------------------------------*/ + +int crypt_verify(Port* port, const char* user, const char* pgpass) { + + char* passwd; + char* valuntil; + char* crypt_pwd; + int retval = STATUS_ERROR; + AbsoluteTime vuntil, + current; + + crypt_getloginfo(user, &passwd, &valuntil); + + if (passwd == NULL || *passwd == '\0') { + if (passwd) free((void*)passwd); + if (valuntil) free((void*)valuntil); + return STATUS_ERROR; + } + + crypt_pwd = crypt(passwd, port->salt); + if (!strcmp(pgpass, crypt_pwd)) { + /* check here to be sure we are not past valuntil + */ + if (!valuntil) + vuntil = INVALID_ABSTIME; + else + vuntil = nabstimein(valuntil); + current = GetCurrentAbsoluteTime(); + if (vuntil != INVALID_ABSTIME && vuntil < current) + retval = STATUS_ERROR; + else + retval = STATUS_OK; + } + + free((void*)passwd); + if (valuntil) free((void*)valuntil); + + return retval; +} diff --git a/src/include/commands/user.h b/src/include/commands/user.h new file mode 100644 index 0000000000..1994b0bcd1 --- /dev/null +++ b/src/include/commands/user.h @@ -0,0 +1,17 @@ +/*------------------------------------------------------------------------- + * + * user.h-- + * + * + * + * + *------------------------------------------------------------------------- + */ +#ifndef USER_H +#define USER_H + +extern void DefineUser(CreateUserStmt *stmt); +extern void AlterUser(AlterUserStmt *stmt); +extern void RemoveUser(char* user); + +#endif /* USER_H */ diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h new file mode 100644 index 0000000000..7c01c8297b --- /dev/null +++ b/src/include/libpq/crypt.h @@ -0,0 +1,20 @@ +/*------------------------------------------------------------------------- + * + * crypt.h-- + * Interface to hba.c + * + * + *------------------------------------------------------------------------- + */ +#ifndef PG_CRYPT_H +#define PG_CRYPT_H + +#include + +#define CRYPT_PWD_FILE "pg_pwd" + +extern char* crypt_getpwdfilename(); +extern MsgType crypt_salt(const char* user); +extern int crypt_verify(Port* port, const char* user, const char* pgpass); + +#endif