user.c 30.2 KB
Newer Older
1 2
/*-------------------------------------------------------------------------
 *
3
 * user.c
4
 *	  Commands for manipulating users and groups.
5
 *
Bruce Momjian's avatar
Add:  
Bruce Momjian committed
6 7
 * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
Tom Lane's avatar
Tom Lane committed
9
 * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.65 2000/07/22 04:16:13 tgl Exp $
10 11 12
 *
 *-------------------------------------------------------------------------
 */
13 14 15 16
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
17

18
#include "postgres.h"
19

Bruce Momjian's avatar
Bruce Momjian committed
20
#include "access/heapam.h"
21 22 23
#include "catalog/catname.h"
#include "catalog/pg_database.h"
#include "catalog/pg_shadow.h"
24 25
#include "catalog/pg_group.h"
#include "catalog/indexing.h"
Bruce Momjian's avatar
Bruce Momjian committed
26
#include "commands/user.h"
27
#include "libpq/crypt.h"
Bruce Momjian's avatar
Bruce Momjian committed
28
#include "miscadmin.h"
29
#include "utils/array.h"
30
#include "utils/builtins.h"
31
#include "utils/fmgroids.h"
32
#include "utils/syscache.h"
33

34 35
static void CheckPgUserAclNotNull(void);

Bruce Momjian's avatar
Bruce Momjian committed
36
#define SQL_LENGTH	512
Marc G. Fournier's avatar
Marc G. Fournier committed
37

38
/*---------------------------------------------------------------------
39
 * write_password_file / update_pg_pwd
40
 *
41
 * copy the modified contents of pg_shadow to a file used by the postmaster
42
 * for user authentication.  The file is stored as $PGDATA/pg_pwd.
43
 *
44 45
 * 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.
46 47
 *---------------------------------------------------------------------
 */
48 49
static void
write_password_file(Relation rel)
50
{
Bruce Momjian's avatar
Bruce Momjian committed
51 52 53
	char	   *filename,
			   *tempname;
	int			bufsize;
54
	FILE	   *fp;
55
	int			flagfd;
56 57 58 59
	mode_t		oumask;
	HeapScanDesc scan;
	HeapTuple	tuple;
	TupleDesc	dsc = RelationGetDescr(rel);
60

61 62 63 64 65 66
	/*
	 * 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();
Marc G. Fournier's avatar
Marc G. Fournier committed
67 68
	bufsize = strlen(filename) + 12;
	tempname = (char *) palloc(bufsize);
69

Marc G. Fournier's avatar
Marc G. Fournier committed
70
	snprintf(tempname, bufsize, "%s.%d", filename, MyProcPid);
71 72 73 74
	oumask = umask((mode_t) 077);
	fp = AllocateFile(tempname, "w");
	umask(oumask);
	if (fp == NULL)
75
		elog(ERROR, "%s: %m", tempname);
76 77 78 79 80 81 82 83 84 85 86

	/* read table */
	scan = heap_beginscan(rel, 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;
87 88

		datum_n = heap_getattr(tuple, Anum_pg_shadow_usename, dsc, &null_n);
89 90
		if (null_n)
			continue;			/* don't allow empty users */
91
		datum_p = heap_getattr(tuple, Anum_pg_shadow_passwd, dsc, &null_p);
92 93 94 95 96 97 98 99 100

		/*
		 * It could be argued that people having a null password shouldn't
		 * be allowed to connect, because they need to have a password set
		 * up first. If you think assuming an empty password in that case
		 * is better, erase the following line.
		 */
		if (null_p)
			continue;
101 102
		datum_v = heap_getattr(tuple, Anum_pg_shadow_valuntil, dsc, &null_v);

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
		/*
		 * These fake entries are not really necessary. To remove them,
		 * the parser in backend/libpq/crypt.c would need to be adjusted.
		 * Initdb might also need adjustments.
		 */
		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",
				nameout(DatumGetName(datum_n)),
125 126
				null_p ? "" :
				DatumGetCString(DirectFunctionCall1(textout, datum_p)),
127 128
				null_v ? "\\N" :
				DatumGetCString(DirectFunctionCall1(nabstimeout, datum_v))
129 130 131
			);
	}
	heap_endscan(scan);
132 133 134 135

	fflush(fp);
	if (ferror(fp))
		elog(ERROR, "%s: %m", tempname);
136 137 138 139 140 141
	FreeFile(fp);

	/*
	 * And rename the temp file to its final name, deleting the old
	 * pg_pwd.
	 */
142 143 144 145 146
	if (rename(tempname, filename))
		elog(ERROR, "rename %s to %s: %m", tempname, filename);

	pfree((void *) tempname);
	pfree((void *) filename);
147 148

	/*
149 150
	 * Create a flag file the postmaster will detect the next time it
	 * tries to authenticate a user.  The postmaster will know to reload
151 152
	 * 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
153
	 * at this point, so let's just make it a NOTICE.
154 155
	 */
	filename = crypt_getpwdreloadfilename();
156 157
	flagfd = BasicOpenFile(filename, O_WRONLY | O_CREAT, 0600);
	if (flagfd < 0)
158 159 160 161
		elog(NOTICE, "%s: %m", filename);
	else
		close(flagfd);
	pfree((void *) filename);
162 163 164 165 166
}



/* This is the wrapper for triggers. */
167 168
Datum
update_pg_pwd(PG_FUNCTION_ARGS)
169
{
170 171 172 173
	Relation	rel = heap_openr(ShadowRelationName, AccessExclusiveLock);

	write_password_file(rel);
	heap_close(rel, AccessExclusiveLock);
174
	return PointerGetDatum(NULL);
175 176
}

177 178 179 180


/*
 * CREATE USER
181
 */
182
void
183
CreateUser(CreateUserStmt *stmt)
184
{
Bruce Momjian's avatar
Bruce Momjian committed
185 186 187 188
	Relation	pg_shadow_rel;
	TupleDesc	pg_shadow_dsc;
	HeapScanDesc scan;
	HeapTuple	tuple;
189 190
	Datum		new_record[Natts_pg_shadow];
	char		new_record_nulls[Natts_pg_shadow];
Bruce Momjian's avatar
Bruce Momjian committed
191
	bool		user_exists = false,
192 193
				sysid_exists = false,
				havesysid;
Bruce Momjian's avatar
Bruce Momjian committed
194
	int			max_id = -1;
195
	List	   *item;
196

197
	havesysid = stmt->sysid > 0;
198

199
	/* Check some permissions first */
200
	if (stmt->password)
201 202
		CheckPgUserAclNotNull();

203 204
	if (!superuser())
		elog(ERROR, "CREATE USER: permission denied");
205

206
	/*
207 208 209 210
	 * Scan the pg_shadow relation to be certain the user or id doesn't
	 * already exist.  Note we secure exclusive lock, because we also need
	 * to be sure of what the next usesysid should be, and we need to
	 * protect our update of the flat password file.
211
	 */
212
	pg_shadow_rel = heap_openr(ShadowRelationName, AccessExclusiveLock);
213
	pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
214

215
	scan = heap_beginscan(pg_shadow_rel, false, SnapshotNow, 0, NULL);
Bruce Momjian's avatar
Bruce Momjian committed
216
	while (!user_exists && !sysid_exists && HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
217
	{
218 219
		Datum		datum;
		bool		null;
Bruce Momjian's avatar
Bruce Momjian committed
220 221 222 223 224

		datum = heap_getattr(tuple, Anum_pg_shadow_usename, pg_shadow_dsc, &null);
		user_exists = datum && !null && (strcmp((char *) datum, stmt->user) == 0);

		datum = heap_getattr(tuple, Anum_pg_shadow_usesysid, pg_shadow_dsc, &null);
225 226 227 228 229 230 231 232
		if (havesysid)			/* customized id wanted */
			sysid_exists = datum && !null && ((int) datum == stmt->sysid);
		else
/* pick 1 + max */
		{
			if ((int) datum > max_id)
				max_id = (int) datum;
		}
233 234 235
	}
	heap_endscan(scan);

Bruce Momjian's avatar
Bruce Momjian committed
236
	if (user_exists || sysid_exists)
237
	{
238
		heap_close(pg_shadow_rel, AccessExclusiveLock);
239 240 241 242
		if (user_exists)
			elog(ERROR, "CREATE USER: user name \"%s\" already exists", stmt->user);
		else
			elog(ERROR, "CREATE USER: sysid %d is already assigned", stmt->sysid);
243 244 245
		return;
	}

246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
	/*
	 * Build a tuple to insert
	 */
	new_record[Anum_pg_shadow_usename - 1] = PointerGetDatum(namein(stmt->user));		/* this truncated
																						 * properly */
	new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(havesysid ? stmt->sysid : max_id + 1);

	AssertState(BoolIsValid(stmt->createdb));
	new_record[Anum_pg_shadow_usecreatedb - 1] = (Datum) (stmt->createdb);
	new_record[Anum_pg_shadow_usetrace - 1] = (Datum) (false);
	AssertState(BoolIsValid(stmt->createuser));
	new_record[Anum_pg_shadow_usesuper - 1] = (Datum) (stmt->createuser);
	/* superuser gets catupd right by default */
	new_record[Anum_pg_shadow_usecatupd - 1] = (Datum) (stmt->createuser);

	if (stmt->password)
262 263
		new_record[Anum_pg_shadow_passwd - 1] =
			DirectFunctionCall1(textin, CStringGetDatum(stmt->password));
264
	if (stmt->validUntil)
265 266
		new_record[Anum_pg_shadow_valuntil - 1] =
			DirectFunctionCall1(nabstimein, CStringGetDatum(stmt->validUntil));
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

	new_record_nulls[Anum_pg_shadow_usename - 1] = ' ';
	new_record_nulls[Anum_pg_shadow_usesysid - 1] = ' ';

	new_record_nulls[Anum_pg_shadow_usecreatedb - 1] = ' ';
	new_record_nulls[Anum_pg_shadow_usetrace - 1] = ' ';
	new_record_nulls[Anum_pg_shadow_usesuper - 1] = ' ';
	new_record_nulls[Anum_pg_shadow_usecatupd - 1] = ' ';

	new_record_nulls[Anum_pg_shadow_passwd - 1] = stmt->password ? ' ' : 'n';
	new_record_nulls[Anum_pg_shadow_valuntil - 1] = stmt->validUntil ? ' ' : 'n';

	tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
	Assert(tuple);

	/*
	 * Insert a new record in the pg_shadow table
	 */
	if (heap_insert(pg_shadow_rel, tuple) == InvalidOid)
		elog(ERROR, "CREATE USER: heap_insert failed");

	/*
	 * Update indexes
	 */
	if (RelationGetForm(pg_shadow_rel)->relhasindex)
	{
		Relation	idescs[Num_pg_shadow_indices];

		CatalogOpenIndices(Num_pg_shadow_indices,
						   Name_pg_shadow_indices, idescs);
		CatalogIndexInsert(idescs, Num_pg_shadow_indices, pg_shadow_rel,
						   tuple);
		CatalogCloseIndices(Num_pg_shadow_indices, idescs);
	}
301 302

	/*
303
	 * Add the user to the groups specified. We'll just call the below
304
	 * AlterGroup for this.
305
	 */
306 307 308
	foreach(item, stmt->groupElts)
	{
		AlterGroupStmt ags;
309

310 311 312 313 314 315
		ags.name = strVal(lfirst(item));		/* the group name to add
												 * this in */
		ags.action = +1;
		ags.listUsers = lcons((void *) makeInteger(havesysid ? stmt->sysid : max_id + 1), NIL);
		AlterGroup(&ags, "CREATE USER");
	}
316

317 318 319
	/*
	 * Write the updated pg_shadow data to the flat password file.
	 */
320 321
	write_password_file(pg_shadow_rel);

322
	/*
323
	 * Now we can clean up.
324
	 */
325
	heap_close(pg_shadow_rel, AccessExclusiveLock);
326 327 328
}


329 330 331 332

/*
 * ALTER USER
 */
333
extern void
334
AlterUser(AlterUserStmt *stmt)
335
{
336 337
	Datum		new_record[Natts_pg_shadow];
	char		new_record_nulls[Natts_pg_shadow];
338 339
	Relation	pg_shadow_rel;
	TupleDesc	pg_shadow_dsc;
340 341 342
	HeapTuple	tuple,
				new_tuple;
	bool		null;
343 344 345 346

	if (stmt->password)
		CheckPgUserAclNotNull();

347 348
	/* must be superuser or just want to change your own password */
	if (!superuser() &&
349
	  !(stmt->createdb == 0 && stmt->createuser == 0 && !stmt->validUntil
350 351
		&& stmt->password && strcmp(GetPgUserName(), stmt->user) == 0))
		elog(ERROR, "ALTER USER: permission denied");
352

353 354 355
	/* changes to the flat password file cannot be rolled back */
	if (IsTransactionBlock() && stmt->password)
		elog(NOTICE, "ALTER USER: password changes cannot be rolled back");
356 357

	/*
358 359 360
	 * Scan the pg_shadow relation to be certain the user exists. Note we
	 * secure exclusive lock to protect our update of the flat password
	 * file.
361
	 */
362
	pg_shadow_rel = heap_openr(ShadowRelationName, AccessExclusiveLock);
363
	pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
364

365
	tuple = SearchSysCacheTuple(SHADOWNAME,
366 367 368
								PointerGetDatum(stmt->user),
								0, 0, 0);
	if (!HeapTupleIsValid(tuple))
369
	{
370
		heap_close(pg_shadow_rel, AccessExclusiveLock);
371
		elog(ERROR, "ALTER USER: user \"%s\" does not exist", stmt->user);
372 373
	}

374 375 376
	/*
	 * Build a tuple to update, perusing the information just obtained
	 */
377 378
	new_record[Anum_pg_shadow_usename - 1] = PointerGetDatum(namein(stmt->user));
	new_record_nulls[Anum_pg_shadow_usename - 1] = ' ';
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429

	/* sysid - leave as is */
	new_record[Anum_pg_shadow_usesysid - 1] = heap_getattr(tuple, Anum_pg_shadow_usesysid, pg_shadow_dsc, &null);
	new_record_nulls[Anum_pg_shadow_usesysid - 1] = null ? 'n' : ' ';

	/* createdb */
	if (stmt->createdb == 0)
	{
		/* don't change */
		new_record[Anum_pg_shadow_usecreatedb - 1] = heap_getattr(tuple, Anum_pg_shadow_usecreatedb, pg_shadow_dsc, &null);
		new_record_nulls[Anum_pg_shadow_usecreatedb - 1] = null ? 'n' : ' ';
	}
	else
	{
		new_record[Anum_pg_shadow_usecreatedb - 1] = (Datum) (stmt->createdb > 0 ? true : false);
		new_record_nulls[Anum_pg_shadow_usecreatedb - 1] = ' ';
	}

	/* trace - leave as is */
	new_record[Anum_pg_shadow_usetrace - 1] = heap_getattr(tuple, Anum_pg_shadow_usetrace, pg_shadow_dsc, &null);
	new_record_nulls[Anum_pg_shadow_usetrace - 1] = null ? 'n' : ' ';

	/* createuser (superuser) */
	if (stmt->createuser == 0)
	{
		/* don't change */
		new_record[Anum_pg_shadow_usesuper - 1] = heap_getattr(tuple, Anum_pg_shadow_usesuper, pg_shadow_dsc, &null);
		new_record_nulls[Anum_pg_shadow_usesuper - 1] = null ? 'n' : ' ';
	}
	else
	{
		new_record[Anum_pg_shadow_usesuper - 1] = (Datum) (stmt->createuser > 0 ? true : false);
		new_record_nulls[Anum_pg_shadow_usesuper - 1] = ' ';
	}

	/* catupd - set to false if someone's superuser priv is being yanked */
	if (stmt->createuser < 0)
	{
		new_record[Anum_pg_shadow_usecatupd - 1] = (Datum) (false);
		new_record_nulls[Anum_pg_shadow_usecatupd - 1] = ' ';
	}
	else
	{
		/* leave alone */
		new_record[Anum_pg_shadow_usecatupd - 1] = heap_getattr(tuple, Anum_pg_shadow_usecatupd, pg_shadow_dsc, &null);
		new_record_nulls[Anum_pg_shadow_usecatupd - 1] = null ? 'n' : ' ';
	}

	/* password */
	if (stmt->password)
	{
430 431
		new_record[Anum_pg_shadow_passwd - 1] =
			DirectFunctionCall1(textin, CStringGetDatum(stmt->password));
432 433 434 435 436
		new_record_nulls[Anum_pg_shadow_passwd - 1] = ' ';
	}
	else
	{
		/* leave as is */
437 438
		new_record[Anum_pg_shadow_passwd - 1] =
			heap_getattr(tuple, Anum_pg_shadow_passwd, pg_shadow_dsc, &null);
439 440 441 442 443 444
		new_record_nulls[Anum_pg_shadow_passwd - 1] = null ? 'n' : ' ';
	}

	/* valid until */
	if (stmt->validUntil)
	{
445 446
		new_record[Anum_pg_shadow_valuntil - 1] =
			DirectFunctionCall1(nabstimein, CStringGetDatum(stmt->validUntil));
447 448 449 450 451
		new_record_nulls[Anum_pg_shadow_valuntil - 1] = ' ';
	}
	else
	{
		/* leave as is */
452 453
		new_record[Anum_pg_shadow_valuntil - 1] =
			heap_getattr(tuple, Anum_pg_shadow_valuntil, pg_shadow_dsc, &null);
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
		new_record_nulls[Anum_pg_shadow_valuntil - 1] = null ? 'n' : ' ';
	}

	new_tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls);
	Assert(new_tuple);
	/* XXX check return value of this? */
	heap_update(pg_shadow_rel, &tuple->t_self, new_tuple, NULL);


	/* Update indexes */
	if (RelationGetForm(pg_shadow_rel)->relhasindex)
	{
		Relation	idescs[Num_pg_shadow_indices];

		CatalogOpenIndices(Num_pg_shadow_indices,
						   Name_pg_shadow_indices, idescs);
		CatalogIndexInsert(idescs, Num_pg_shadow_indices, pg_shadow_rel,
						   tuple);
		CatalogCloseIndices(Num_pg_shadow_indices, idescs);
	}
474

475 476 477
	/*
	 * Write the updated pg_shadow data to the flat password file.
	 */
478
	write_password_file(pg_shadow_rel);
479

480 481 482 483
	/*
	 * Now we can clean up.
	 */
	heap_close(pg_shadow_rel, AccessExclusiveLock);
484

485 486 487
}


488 489 490 491 492 493

/*
 * DROP USER
 */
void
DropUser(DropUserStmt *stmt)
494
{
495 496
	Relation	pg_shadow_rel;
	TupleDesc	pg_shadow_dsc;
497
	List	   *item;
498

499 500
	if (!superuser())
		elog(ERROR, "DROP USER: permission denied");
501

502
	if (IsTransactionBlock())
503
		elog(NOTICE, "DROP USER cannot be rolled back completely");
504 505

	/*
506 507 508
	 * Scan the pg_shadow relation to find the usesysid of the user to be
	 * deleted.  Note we secure exclusive lock, because we need to protect
	 * our update of the flat password file.
509
	 */
510
	pg_shadow_rel = heap_openr(ShadowRelationName, AccessExclusiveLock);
511
	pg_shadow_dsc = RelationGetDescr(pg_shadow_rel);
512

513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
	foreach(item, stmt->users)
	{
		HeapTuple	tuple,
					tmp_tuple;
		Relation	pg_rel;
		TupleDesc	pg_dsc;
		ScanKeyData scankey;
		HeapScanDesc scan;
		Datum		datum;
		bool		null;
		int32		usesysid;
		const char *user = strVal(lfirst(item));

		tuple = SearchSysCacheTuple(SHADOWNAME,
									PointerGetDatum(user),
									0, 0, 0);
		if (!HeapTupleIsValid(tuple))
		{
			heap_close(pg_shadow_rel, AccessExclusiveLock);
			elog(ERROR, "DROP USER: user \"%s\" does not exist%s", user,
				 (length(stmt->users) > 1) ? " (no users removed)" : "");
		}

		usesysid = DatumGetInt32(heap_getattr(tuple, Anum_pg_shadow_usesysid, pg_shadow_dsc, &null));

		/*-------------------
		 * Check if user still owns a database. If so, error out.
		 *
		 * (It used to be that this function would drop the database automatically.
		 *	This is not only very dangerous for people that don't read the manual,
		 *	it doesn't seem to be the behaviour one would expect either.)
		 *													 -- petere 2000/01/14)
		 *-------------------*/
		pg_rel = heap_openr(DatabaseRelationName, AccessExclusiveLock);
		pg_dsc = RelationGetDescr(pg_rel);

		ScanKeyEntryInitialize(&scankey, 0x0, Anum_pg_database_datdba, F_INT4EQ,
							   Int32GetDatum(usesysid));

		scan = heap_beginscan(pg_rel, false, SnapshotNow, 1, &scankey);

		if (HeapTupleIsValid(tmp_tuple = heap_getnext(scan, 0)))
		{
			datum = heap_getattr(tmp_tuple, Anum_pg_database_datname, pg_dsc, &null);
			heap_close(pg_shadow_rel, AccessExclusiveLock);
			elog(ERROR, "DROP USER: user \"%s\" owns database \"%s\", cannot be removed%s",
				 user, nameout(DatumGetName(datum)),
				 (length(stmt->users) > 1) ? " (no users removed)" : ""
				);
		}

		heap_endscan(scan);
		heap_close(pg_rel, AccessExclusiveLock);

		/*
		 * Somehow we'd have to check for tables, views, etc. owned by the
		 * user as well, but those could be spread out over all sorts of
		 * databases which we don't have access to (easily).
		 */

		/*
		 * Remove the user from the pg_shadow table
		 */
		heap_delete(pg_shadow_rel, &tuple->t_self, NULL);

		/*
		 * Remove user from groups
		 *
		 * try calling alter group drop user for every group
		 */
		pg_rel = heap_openr(GroupRelationName, AccessExclusiveLock);
		pg_dsc = RelationGetDescr(pg_rel);
		scan = heap_beginscan(pg_rel, false, SnapshotNow, 0, NULL);
		while (HeapTupleIsValid(tmp_tuple = heap_getnext(scan, 0)))
		{
			AlterGroupStmt ags;

			datum = heap_getattr(tmp_tuple, Anum_pg_group_groname, pg_dsc, &null);

			ags.name = nameout(DatumGetName(datum));	/* the group name from
														 * which to try to drop
														 * the user */
			ags.action = -1;
			ags.listUsers = lcons((void *) makeInteger(usesysid), NIL);
			AlterGroup(&ags, "DROP USER");
		}
		heap_endscan(scan);
		heap_close(pg_rel, AccessExclusiveLock);
	}
602

603
	/*
604
	 * Write the updated pg_shadow data to the flat password file.
605
	 */
606
	write_password_file(pg_shadow_rel);
607

608 609 610 611
	/*
	 * Now we can clean up.
	 */
	heap_close(pg_shadow_rel, AccessExclusiveLock);
612
}
613

614 615


616 617 618
/*
 * CheckPgUserAclNotNull
 *
619
 * check to see if there is an ACL on pg_shadow
620
 */
621 622
static void
CheckPgUserAclNotNull()
623
{
624
	HeapTuple	htup;
625

626
	htup = SearchSysCacheTuple(RELNAME,
627 628
							   PointerGetDatum(ShadowRelationName),
							   0, 0, 0);
629
	if (!HeapTupleIsValid(htup))
630
	{
631
		/* BIG problem */
632
		elog(ERROR, "IsPgUserAclNull: \"%s\" not found",
633
			 ShadowRelationName);
634 635
	}

636
	if (heap_attisnull(htup, Anum_pg_class_relacl))
637
	{
638
		elog(ERROR,
639 640 641 642
			 "To use passwords, you have to revoke permissions on %s "
			 "so normal users cannot read the passwords. "
			 "Try 'REVOKE ALL ON \"%s\" FROM PUBLIC'.",
			 ShadowRelationName, ShadowRelationName);
643
	}
644

645 646
	return;
}
647 648 649



650 651 652
/*
 * CREATE GROUP
 */
653
void
654
CreateGroup(CreateGroupStmt *stmt)
655 656 657 658
{
	Relation	pg_group_rel;
	HeapScanDesc scan;
	HeapTuple	tuple;
659 660 661 662 663 664 665 666 667
	TupleDesc	pg_group_dsc;
	bool		group_exists = false,
				sysid_exists = false;
	int			max_id = 0;
	Datum		new_record[Natts_pg_group];
	char		new_record_nulls[Natts_pg_group];
	List	   *item,
			   *newlist = NULL;
	ArrayType  *userarray;
668 669 670 671

	/*
	 * Make sure the user can do this.
	 */
672 673 674
	if (!superuser())
		elog(ERROR, "CREATE GROUP: permission denied");

675 676 677 678 679 680
	pg_group_rel = heap_openr(GroupRelationName, AccessExclusiveLock);
	pg_group_dsc = RelationGetDescr(pg_group_rel);

	scan = heap_beginscan(pg_group_rel, false, SnapshotNow, 0, NULL);
	while (!group_exists && !sysid_exists && HeapTupleIsValid(tuple = heap_getnext(scan, false)))
	{
681 682
		Datum		datum;
		bool		null;
683 684 685 686 687

		datum = heap_getattr(tuple, Anum_pg_group_groname, pg_group_dsc, &null);
		group_exists = datum && !null && (strcmp((char *) datum, stmt->name) == 0);

		datum = heap_getattr(tuple, Anum_pg_group_grosysid, pg_group_dsc, &null);
688 689 690 691 692 693 694 695
		if (stmt->sysid >= 0)	/* customized id wanted */
			sysid_exists = datum && !null && ((int) datum == stmt->sysid);
		else
/* pick 1 + max */
		{
			if ((int) datum > max_id)
				max_id = (int) datum;
		}
696 697 698 699 700 701
	}
	heap_endscan(scan);

	if (group_exists || sysid_exists)
	{
		heap_close(pg_group_rel, AccessExclusiveLock);
702 703 704 705
		if (group_exists)
			elog(ERROR, "CREATE GROUP: group name \"%s\" already exists", stmt->name);
		else
			elog(ERROR, "CREATE GROUP: group sysid %d is already assigned", stmt->sysid);
706 707
	}

708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
	/*
	 * Translate the given user names to ids
	 */

	foreach(item, stmt->initUsers)
	{
		const char *groupuser = strVal(lfirst(item));
		Value	   *v;

		tuple = SearchSysCacheTuple(SHADOWNAME,
									PointerGetDatum(groupuser),
									0, 0, 0);
		if (!HeapTupleIsValid(tuple))
		{
			heap_close(pg_group_rel, AccessExclusiveLock);
			elog(ERROR, "CREATE GROUP: user \"%s\" does not exist", groupuser);
		}

		v = makeInteger(((Form_pg_shadow) GETSTRUCT(tuple))->usesysid);
		if (!member(v, newlist))
			newlist = lcons(v, newlist);
	}

	/* build an array to insert */
	if (newlist)
	{
		int			i;

		userarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
Tom Lane's avatar
Tom Lane committed
737 738
		userarray->size = ARR_OVERHEAD(1) + length(newlist) * sizeof(int32);
		userarray->flags = 0;
739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
		ARR_NDIM(userarray) = 1;/* one dimensional array */
		ARR_LBOUND(userarray)[0] = 1;	/* axis starts at one */
		ARR_DIMS(userarray)[0] = length(newlist);		/* axis is this long */
		/* fill the array */
		i = 0;
		foreach(item, newlist)
			((int *) ARR_DATA_PTR(userarray))[i++] = intVal(lfirst(item));
	}
	else
		userarray = NULL;

	/*
	 * Form a tuple to insert
	 */
	if (stmt->sysid >= 0)
		max_id = stmt->sysid;
	else
		max_id++;

	new_record[Anum_pg_group_groname - 1] = (Datum) (stmt->name);
	new_record[Anum_pg_group_grosysid - 1] = (Datum) (max_id);
	new_record[Anum_pg_group_grolist - 1] = (Datum) userarray;

	new_record_nulls[Anum_pg_group_groname - 1] = ' ';
	new_record_nulls[Anum_pg_group_grosysid - 1] = ' ';
	new_record_nulls[Anum_pg_group_grolist - 1] = userarray ? ' ' : 'n';

	tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);

	/*
	 * Insert a new record in the pg_group_table
	 */
	heap_insert(pg_group_rel, tuple);

	/*
	 * Update indexes
	 */
	if (RelationGetForm(pg_group_rel)->relhasindex)
	{
		Relation	idescs[Num_pg_group_indices];

		CatalogOpenIndices(Num_pg_group_indices,
						   Name_pg_group_indices, idescs);
		CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel,
						   tuple);
		CatalogCloseIndices(Num_pg_group_indices, idescs);
	}
786

787
	heap_close(pg_group_rel, AccessExclusiveLock);
788 789 790 791
}



792 793 794
/*
 * ALTER GROUP
 */
795
void
796
AlterGroup(AlterGroupStmt *stmt, const char *tag)
797 798
{
	Relation	pg_group_rel;
799 800
	TupleDesc	pg_group_dsc;
	HeapTuple	group_tuple;
801

802
	/*
803 804
	 * Make sure the user can do this.
	 */
805 806 807
	if (!superuser())
		elog(ERROR, "%s: permission denied", tag);

808 809
	pg_group_rel = heap_openr(GroupRelationName, AccessExclusiveLock);
	pg_group_dsc = RelationGetDescr(pg_group_rel);
810

811 812 813 814 815
	/*
	 * Verify that group exists. If we find a tuple, will take that the
	 * rest of the way and make our modifications on it.
	 */
	if (!HeapTupleIsValid(group_tuple = SearchSysCacheTupleCopy(GRONAME, PointerGetDatum(stmt->name), 0, 0, 0)))
816
	{
817
		heap_close(pg_group_rel, AccessExclusiveLock);
818
		elog(ERROR, "%s: group \"%s\" does not exist", tag, stmt->name);
819 820
	}

821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881
	AssertState(stmt->action == +1 || stmt->action == -1);

	/*
	 * Now decide what to do.
	 */
	if (stmt->action == +1)		/* add users, might also be invoked by
								 * create user */
	{
		Datum		new_record[Natts_pg_group];
		char		new_record_nulls[Natts_pg_group] = {' ', ' ', ' '};
		ArrayType  *newarray,
				   *oldarray;
		List	   *newlist = NULL,
				   *item;
		HeapTuple	tuple;
		bool		null = false;
		Datum		datum = heap_getattr(group_tuple, Anum_pg_group_grolist, pg_group_dsc, &null);
		int			i;

		oldarray = (ArrayType *) datum;
		Assert(null || ARR_NDIM(oldarray) == 1);
		/* first add the old array to the hitherto empty list */
		if (!null)
			for (i = ARR_LBOUND(oldarray)[0]; i < ARR_LBOUND(oldarray)[0] + ARR_DIMS(oldarray)[0]; i++)
			{
				int			index,
							arrval;
				Value	   *v;
				bool		valueNull;

				index = i;
				arrval = DatumGetInt32(array_ref(oldarray, 1, &index, true /* by value */ ,
											sizeof(int), 0, &valueNull));
				v = makeInteger(arrval);
				/* filter out duplicates */
				if (!member(v, newlist))
					newlist = lcons(v, newlist);
			}

		/*
		 * now convert the to be added usernames to sysids and add them to
		 * the list
		 */
		foreach(item, stmt->listUsers)
		{
			Value	   *v;

			if (strcmp(tag, "ALTER GROUP") == 0)
			{
				/* Get the uid of the proposed user to add. */
				tuple = SearchSysCacheTuple(SHADOWNAME,
								   PointerGetDatum(strVal(lfirst(item))),
											0, 0, 0);
				if (!HeapTupleIsValid(tuple))
				{
					heap_close(pg_group_rel, AccessExclusiveLock);
					elog(ERROR, "%s: user \"%s\" does not exist", tag, strVal(lfirst(item)));
				}
				v = makeInteger(((Form_pg_shadow) GETSTRUCT(tuple))->usesysid);
			}
			else if (strcmp(tag, "CREATE USER") == 0)
882
			{
883 884 885 886 887 888 889 890 891 892

				/*
				 * in this case we already know the uid and it wouldn't be
				 * in the cache anyway yet
				 */
				v = lfirst(item);
			}
			else
			{
				elog(ERROR, "AlterGroup: unknown tag %s", tag);
893 894
				v = NULL;		/* keep compiler quiet */
			}
895

896 897 898 899 900 901 902 903 904 905 906 907
			if (!member(v, newlist))
				newlist = lcons(v, newlist);
			else

				/*
				 * we silently assume here that this error will only come
				 * up in a ALTER GROUP statement
				 */
				elog(NOTICE, "%s: user \"%s\" is already in group \"%s\"", tag, strVal(lfirst(item)), stmt->name);
		}

		newarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
Tom Lane's avatar
Tom Lane committed
908 909
		newarray->size = ARR_OVERHEAD(1) + length(newlist) * sizeof(int32);
		newarray->flags = 0;
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015
		ARR_NDIM(newarray) = 1; /* one dimensional array */
		ARR_LBOUND(newarray)[0] = 1;	/* axis starts at one */
		ARR_DIMS(newarray)[0] = length(newlist);		/* axis is this long */
		/* fill the array */
		i = 0;
		foreach(item, newlist)
			((int *) ARR_DATA_PTR(newarray))[i++] = intVal(lfirst(item));

		/*
		 * Form a tuple with the new array and write it back.
		 */
		new_record[Anum_pg_group_groname - 1] = (Datum) (stmt->name);
		new_record[Anum_pg_group_grosysid - 1] = heap_getattr(group_tuple, Anum_pg_group_grosysid, pg_group_dsc, &null);
		new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);

		tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
		heap_update(pg_group_rel, &group_tuple->t_self, tuple, NULL);

		/* Update indexes */
		if (RelationGetForm(pg_group_rel)->relhasindex)
		{
			Relation	idescs[Num_pg_group_indices];

			CatalogOpenIndices(Num_pg_group_indices,
							   Name_pg_group_indices, idescs);
			CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel,
							   tuple);
			CatalogCloseIndices(Num_pg_group_indices, idescs);
		}
	}							/* endif alter group add user */

	else if (stmt->action == -1)/* drop users from group */
	{
		Datum		datum;
		bool		null;
		bool		is_dropuser = strcmp(tag, "DROP USER") == 0;

		datum = heap_getattr(group_tuple, Anum_pg_group_grolist, pg_group_dsc, &null);
		if (null)
		{
			if (!is_dropuser)
				elog(NOTICE, "ALTER GROUP: group \"%s\" does not have any members", stmt->name);
		}
		else
		{
			HeapTuple	tuple;
			Datum		new_record[Natts_pg_group];
			char		new_record_nulls[Natts_pg_group] = {' ', ' ', ' '};
			ArrayType  *oldarray,
					   *newarray;
			List	   *newlist = NULL,
					   *item;
			int			i;

			oldarray = (ArrayType *) datum;
			Assert(ARR_NDIM(oldarray) == 1);
			/* first add the old array to the hitherto empty list */
			for (i = ARR_LBOUND(oldarray)[0]; i < ARR_LBOUND(oldarray)[0] + ARR_DIMS(oldarray)[0]; i++)
			{
				int			index,
							arrval;
				Value	   *v;
				bool		valueNull;

				index = i;
				arrval = DatumGetInt32(array_ref(oldarray, 1, &index, true /* by value */ ,
											sizeof(int), 0, &valueNull));
				v = makeInteger(arrval);
				/* filter out duplicates */
				if (!member(v, newlist))
					newlist = lcons(v, newlist);
			}

			/*
			 * now convert the to be dropped usernames to sysids and
			 * remove them from the list
			 */
			foreach(item, stmt->listUsers)
			{
				Value	   *v;

				if (!is_dropuser)
				{
					/* Get the uid of the proposed user to drop. */
					tuple = SearchSysCacheTuple(SHADOWNAME,
								   PointerGetDatum(strVal(lfirst(item))),
												0, 0, 0);
					if (!HeapTupleIsValid(tuple))
					{
						heap_close(pg_group_rel, AccessExclusiveLock);
						elog(ERROR, "ALTER GROUP: user \"%s\" does not exist", strVal(lfirst(item)));
					}
					v = makeInteger(((Form_pg_shadow) GETSTRUCT(tuple))->usesysid);
				}
				else
				{
					/* for dropuser we already know the uid */
					v = lfirst(item);
				}
				if (member(v, newlist))
					newlist = LispRemove(v, newlist);
				else if (!is_dropuser)
					elog(NOTICE, "ALTER GROUP: user \"%s\" is not in group \"%s\"", strVal(lfirst(item)), stmt->name);
			}

			newarray = palloc(ARR_OVERHEAD(1) + length(newlist) * sizeof(int32));
Tom Lane's avatar
Tom Lane committed
1016 1017
			newarray->size = ARR_OVERHEAD(1) + length(newlist) * sizeof(int32);
			newarray->flags = 0;
1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
			ARR_NDIM(newarray) = 1;		/* one dimensional array */
			ARR_LBOUND(newarray)[0] = 1;		/* axis starts at one */
			ARR_DIMS(newarray)[0] = length(newlist);	/* axis is this long */
			/* fill the array */
			i = 0;
			foreach(item, newlist)
				((int *) ARR_DATA_PTR(newarray))[i++] = intVal(lfirst(item));

			/*
			 * Insert the new tuple with the updated user list
			 */
			new_record[Anum_pg_group_groname - 1] = (Datum) (stmt->name);
			new_record[Anum_pg_group_grosysid - 1] = heap_getattr(group_tuple, Anum_pg_group_grosysid, pg_group_dsc, &null);
			new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray);

			tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls);
			heap_update(pg_group_rel, &group_tuple->t_self, tuple, NULL);

			/* Update indexes */
			if (RelationGetForm(pg_group_rel)->relhasindex)
			{
				Relation	idescs[Num_pg_group_indices];

				CatalogOpenIndices(Num_pg_group_indices,
								   Name_pg_group_indices, idescs);
				CatalogIndexInsert(idescs, Num_pg_group_indices, pg_group_rel,
								   tuple);
				CatalogCloseIndices(Num_pg_group_indices, idescs);
			}

		}						/* endif group not null */
	}							/* endif alter group drop user */

	heap_close(pg_group_rel, AccessExclusiveLock);

	pfree(group_tuple);
1054 1055 1056 1057
}



1058 1059 1060
/*
 * DROP GROUP
 */
1061
void
1062
DropGroup(DropGroupStmt *stmt)
1063 1064 1065 1066
{
	Relation	pg_group_rel;
	HeapScanDesc scan;
	HeapTuple	tuple;
1067 1068
	TupleDesc	pg_group_dsc;
	bool		gro_exists = false;
1069

1070
	/*
1071 1072
	 * Make sure the user can do this.
	 */
1073 1074
	if (!superuser())
		elog(ERROR, "DROP GROUP: permission denied");
1075

1076 1077 1078
	/*
	 * Scan the pg_group table and delete all matching groups.
	 */
1079 1080 1081 1082 1083 1084
	pg_group_rel = heap_openr(GroupRelationName, AccessExclusiveLock);
	pg_group_dsc = RelationGetDescr(pg_group_rel);
	scan = heap_beginscan(pg_group_rel, false, SnapshotNow, 0, NULL);

	while (HeapTupleIsValid(tuple = heap_getnext(scan, false)))
	{
1085 1086 1087 1088 1089 1090 1091 1092 1093
		Datum		datum;
		bool		null;

		datum = heap_getattr(tuple, Anum_pg_group_groname, pg_group_dsc, &null);
		if (datum && !null && strcmp((char *) datum, stmt->name) == 0)
		{
			gro_exists = true;
			heap_delete(pg_group_rel, &tuple->t_self, NULL);
		}
1094 1095 1096 1097
	}

	heap_endscan(scan);

1098 1099 1100 1101 1102 1103
	/*
	 * Did we find any?
	 */
	if (!gro_exists)
	{
		heap_close(pg_group_rel, AccessExclusiveLock);
1104
		elog(ERROR, "DROP GROUP: group \"%s\" does not exist", stmt->name);
1105
	}
1106

1107
	heap_close(pg_group_rel, AccessExclusiveLock);
1108
}