1 /*-------------------------------------------------------------------------
4 * Routines to check access control permissions.
6 * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * $Header: /cvsroot/pgsql/src/backend/catalog/aclchk.c,v 1.53 2001/11/05 17:46:24 momjian Exp $
16 *-------------------------------------------------------------------------
20 #include "access/heapam.h"
21 #include "catalog/catalog.h"
22 #include "catalog/catname.h"
23 #include "catalog/indexing.h"
24 #include "catalog/pg_aggregate.h"
25 #include "catalog/pg_group.h"
26 #include "catalog/pg_operator.h"
27 #include "catalog/pg_proc.h"
28 #include "catalog/pg_shadow.h"
29 #include "catalog/pg_type.h"
30 #include "miscadmin.h"
31 #include "parser/parse_agg.h"
32 #include "parser/parse_func.h"
33 #include "utils/acl.h"
34 #include "utils/syscache.h"
35 #include "utils/temprel.h"
37 static int32 aclcheck(Acl *acl, AclId id, AclIdType idtype, AclMode mode);
39 /* warning messages, now more explicit. */
40 /* MUST correspond to the order of the ACLCHK_* result codes in acl.h. */
41 char *aclcheck_error_strings[] = {
44 "Table does not exist.",
45 "Must be table owner."
56 elog(DEBUG, "acl size = %d, # acls = %d",
57 ACL_SIZE(acl), ACL_NUM(acl));
59 for (i = 0; i < ACL_NUM(acl); ++i)
60 elog(DEBUG, " acl[%d]: %s", i,
61 DatumGetCString(DirectFunctionCall1(aclitemout,
62 PointerGetDatum(aip + i))));
68 * Called to execute the utility commands GRANT and REVOKE
71 ExecuteGrantStmt(GrantStmt *stmt)
76 /* see comment in pg_type.h */
77 Assert(ACLITEMSIZE == sizeof(AclItem));
79 foreach(i, stmt->relnames)
81 char *relname = strVal(lfirst(i));
84 Form_pg_class pg_class_tuple;
91 Datum values[Natts_pg_class];
92 char nulls[Natts_pg_class];
93 char replaces[Natts_pg_class];
96 if (!pg_ownercheck(GetUserId(), relname, RELNAME))
97 elog(ERROR, "permission denied");
100 relation = heap_openr(RelationRelationName, RowExclusiveLock);
101 tuple = SearchSysCache(RELNAME,
102 PointerGetDatum(relname),
104 if (!HeapTupleIsValid(tuple))
106 heap_close(relation, RowExclusiveLock);
107 elog(ERROR, "relation \"%s\" not found",
110 pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
112 if (pg_class_tuple->relkind == RELKIND_INDEX)
113 elog(ERROR, "\"%s\" is an index",
117 * If there's no ACL, create a default using the pg_class.relowner
120 aclDatum = SysCacheGetAttr(RELNAME, tuple, Anum_pg_class_relacl,
123 old_acl = acldefault(relname, pg_class_tuple->relowner);
125 /* get a detoasted copy of the rel's ACL */
126 old_acl = DatumGetAclPCopy(aclDatum);
133 foreach(j, stmt->grantees)
135 PrivGrantee *grantee = (PrivGrantee *) lfirst(j);
141 if (grantee->username)
142 granteeString = aclmakeuser("U", grantee->username);
143 else if (grantee->groupname)
144 granteeString = aclmakeuser("G", grantee->groupname);
146 granteeString = aclmakeuser("A", "");
148 aclString = makeAclString(stmt->privileges, granteeString,
149 stmt->is_grant ? '+' : '-');
151 /* Convert string ACL spec into internal form */
152 aclparse(aclString, &aclitem, &modechg);
153 new_acl = aclinsert3(new_acl, &aclitem, modechg);
159 /* finished building new ACL value, now insert it */
160 for (i = 0; i < Natts_pg_class; ++i)
163 nulls[i] = ' '; /* ignored if replaces[i]==' ' anyway */
164 values[i] = (Datum) NULL; /* ignored if replaces[i]==' '
167 replaces[Anum_pg_class_relacl - 1] = 'r';
168 values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl);
169 newtuple = heap_modifytuple(tuple, relation, values, nulls, replaces);
171 ReleaseSysCache(tuple);
173 simple_heap_update(relation, &newtuple->t_self, newtuple);
176 /* keep the catalog indexes up to date */
177 Relation idescs[Num_pg_class_indices];
179 CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices,
181 CatalogIndexInsert(idescs, Num_pg_class_indices, relation, newtuple);
182 CatalogCloseIndices(Num_pg_class_indices, idescs);
188 heap_close(relation, RowExclusiveLock);
195 get_grosysid(char *groname)
200 tuple = SearchSysCache(GRONAME,
201 PointerGetDatum(groname),
203 if (HeapTupleIsValid(tuple))
205 id = ((Form_pg_group) GETSTRUCT(tuple))->grosysid;
206 ReleaseSysCache(tuple);
209 elog(ERROR, "non-existent group \"%s\"", groname);
214 * Convert group ID to name, or return NULL if group can't be found
217 get_groname(AclId grosysid)
222 tuple = SearchSysCache(GROSYSID,
223 ObjectIdGetDatum(grosysid),
225 if (HeapTupleIsValid(tuple))
227 name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tuple))->groname));
228 ReleaseSysCache(tuple);
234 * Is user a member of group?
237 in_group(AclId uid, AclId gid)
248 tuple = SearchSysCache(GROSYSID,
249 ObjectIdGetDatum(gid),
251 if (HeapTupleIsValid(tuple))
253 att = SysCacheGetAttr(GROSYSID,
255 Anum_pg_group_grolist,
259 /* be sure the IdList is not toasted */
260 glist = DatumGetIdListP(att);
262 num = IDLIST_NUM(glist);
263 aidp = IDLIST_DAT(glist);
264 for (i = 0; i < num; ++i)
272 /* if IdList was toasted, free detoasted copy */
273 if ((Pointer) glist != DatumGetPointer(att))
276 ReleaseSysCache(tuple);
279 elog(NOTICE, "in_group: group %u not found", gid);
286 * Returns ACLCHECK_OK if the 'id' of type 'idtype' has ACL entries in 'acl'
287 * to satisfy any one of the requirements of 'mode'. Returns an appropriate
288 * ACLCHECK_* error code otherwise.
290 * The ACL list is expected to be sorted in standard order.
293 aclcheck(Acl *acl, AclId id, AclIdType idtype, AclMode mode)
301 * If ACL is null, default to "OK" --- this should not happen, since
302 * caller should have inserted appropriate default
306 elog(DEBUG, "aclcheck: null ACL, returning OK");
311 aidat = ACL_DAT(acl);
314 * We'll treat the empty ACL like that, too, although this is more
315 * like an error (i.e., you manually blew away your ACL array) -- the
316 * system never creates an empty ACL, since there must always be a
317 * "world" entry in the first slot.
321 elog(DEBUG, "aclcheck: zero-length ACL, returning OK");
326 * "World" rights are applicable regardless of the passed-in ID, and
327 * since they're much the cheapest to check, check 'em first.
329 if (aidat->ai_idtype != ACL_IDTYPE_WORLD)
330 elog(ERROR, "aclcheck: first entry in ACL is not 'world' entry");
331 if (aidat->ai_mode & mode)
334 elog(DEBUG, "aclcheck: using world=%d", aidat->ai_mode);
342 /* See if permission is granted directly to user */
343 for (i = 1, aip = aidat + 1; /* skip world entry */
344 i < num && aip->ai_idtype == ACL_IDTYPE_UID;
347 if (aip->ai_id == id)
350 elog(DEBUG, "aclcheck: found user %u/%d",
351 aip->ai_id, aip->ai_mode);
353 if (aip->ai_mode & mode)
357 /* See if he has the permission via any group */
359 i < num && aip->ai_idtype == ACL_IDTYPE_GID;
362 if (aip->ai_mode & mode)
364 if (in_group(id, aip->ai_id))
367 elog(DEBUG, "aclcheck: found group %u/%d",
368 aip->ai_id, aip->ai_mode);
376 /* Look for this group ID */
377 for (i = 1, aip = aidat + 1; /* skip world entry */
378 i < num && aip->ai_idtype == ACL_IDTYPE_UID;
380 /* skip UID entry */ ;
382 i < num && aip->ai_idtype == ACL_IDTYPE_GID;
385 if (aip->ai_id == id)
388 elog(DEBUG, "aclcheck: found group %u/%d",
389 aip->ai_id, aip->ai_mode);
391 if (aip->ai_mode & mode)
396 case ACL_IDTYPE_WORLD:
397 /* Only check the world entry */
400 elog(ERROR, "aclcheck: bogus ACL id type: %d", idtype);
404 /* If get here, he doesn't have the privilege nohow */
405 return ACLCHECK_NO_PRIV;
409 * Exported routine for checking a user's access privileges to a table
411 * Returns an ACLCHECK_* result code.
414 pg_aclcheck(char *relname, Oid userid, AclMode mode)
424 * Validate userid, find out if he is superuser
426 tuple = SearchSysCache(SHADOWSYSID,
427 ObjectIdGetDatum(userid),
429 if (!HeapTupleIsValid(tuple))
430 elog(ERROR, "pg_aclcheck: invalid user id %u",
433 usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
436 * Deny anyone permission to update a system catalog unless
437 * pg_shadow.usecatupd is set. (This is to let superusers protect
438 * themselves from themselves.)
440 if ((mode & (ACL_INSERT | ACL_UPDATE | ACL_DELETE)) &&
441 !allowSystemTableMods && IsSystemRelationName(relname) &&
442 !is_temp_relname(relname) &&
443 !((Form_pg_shadow) GETSTRUCT(tuple))->usecatupd)
446 elog(DEBUG, "pg_aclcheck: catalog update to \"%s\": permission denied",
449 ReleaseSysCache(tuple);
450 return ACLCHECK_NO_PRIV;
454 * Otherwise, superusers bypass all permission-checking.
456 if (((Form_pg_shadow) GETSTRUCT(tuple))->usesuper)
459 elog(DEBUG, "pg_aclcheck: \"%s\" is superuser",
462 ReleaseSysCache(tuple);
466 ReleaseSysCache(tuple);
467 /* caution: usename is inaccessible beyond this point... */
470 * Normal case: get the relation's ACL from pg_class
472 tuple = SearchSysCache(RELNAME,
473 PointerGetDatum(relname),
475 if (!HeapTupleIsValid(tuple))
476 elog(ERROR, "pg_aclcheck: class \"%s\" not found", relname);
478 aclDatum = SysCacheGetAttr(RELNAME, tuple, Anum_pg_class_relacl,
482 /* No ACL, so build default ACL for rel */
485 ownerId = ((Form_pg_class) GETSTRUCT(tuple))->relowner;
486 acl = acldefault(relname, ownerId);
487 aclDatum = (Datum) 0;
491 /* detoast rel's ACL if necessary */
492 acl = DatumGetAclP(aclDatum);
495 result = aclcheck(acl, userid, (AclIdType) ACL_IDTYPE_UID, mode);
497 /* if we have a detoasted copy, free it */
498 if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
501 ReleaseSysCache(tuple);
507 * Check ownership of an object identified by name (which will be looked
508 * up in the system cache identified by cacheid).
510 * Returns true if userid owns the item, or should be allowed to modify
511 * the item as if he owned it.
514 pg_ownercheck(Oid userid,
522 tuple = SearchSysCache(SHADOWSYSID,
523 ObjectIdGetDatum(userid),
525 if (!HeapTupleIsValid(tuple))
526 elog(ERROR, "pg_ownercheck: invalid user id %u",
528 usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
531 * Superusers bypass all permission-checking.
533 if (((Form_pg_shadow) GETSTRUCT(tuple))->usesuper)
536 elog(DEBUG, "pg_ownercheck: user \"%s\" is superuser",
539 ReleaseSysCache(tuple);
543 ReleaseSysCache(tuple);
544 /* caution: usename is inaccessible beyond this point... */
546 tuple = SearchSysCache(cacheid,
547 PointerGetDatum(name),
552 if (!HeapTupleIsValid(tuple))
553 elog(ERROR, "pg_ownercheck: class \"%s\" not found",
555 owner_id = ((Form_pg_class) GETSTRUCT(tuple))->relowner;
558 if (!HeapTupleIsValid(tuple))
559 elog(ERROR, "pg_ownercheck: type \"%s\" not found",
561 owner_id = ((Form_pg_type) GETSTRUCT(tuple))->typowner;
564 elog(ERROR, "pg_ownercheck: invalid cache id: %d", cacheid);
565 owner_id = 0; /* keep compiler quiet */
569 ReleaseSysCache(tuple);
571 return userid == owner_id;
575 * Ownership check for an operator (specified by OID).
578 pg_oper_ownercheck(Oid userid, Oid oprid)
584 tuple = SearchSysCache(SHADOWSYSID,
585 ObjectIdGetDatum(userid),
587 if (!HeapTupleIsValid(tuple))
588 elog(ERROR, "pg_oper_ownercheck: invalid user id %u",
590 usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
593 * Superusers bypass all permission-checking.
595 if (((Form_pg_shadow) GETSTRUCT(tuple))->usesuper)
598 elog(DEBUG, "pg_ownercheck: user \"%s\" is superuser",
601 ReleaseSysCache(tuple);
605 ReleaseSysCache(tuple);
606 /* caution: usename is inaccessible beyond this point... */
608 tuple = SearchSysCache(OPEROID,
609 ObjectIdGetDatum(oprid),
611 if (!HeapTupleIsValid(tuple))
612 elog(ERROR, "pg_ownercheck: operator %u not found",
615 owner_id = ((Form_pg_operator) GETSTRUCT(tuple))->oprowner;
617 ReleaseSysCache(tuple);
619 return userid == owner_id;
623 * Ownership check for a function (specified by name and argument types).
626 pg_func_ownercheck(Oid userid,
635 tuple = SearchSysCache(SHADOWSYSID,
636 ObjectIdGetDatum(userid),
638 if (!HeapTupleIsValid(tuple))
639 elog(ERROR, "pg_func_ownercheck: invalid user id %u",
641 usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
644 * Superusers bypass all permission-checking.
646 if (((Form_pg_shadow) GETSTRUCT(tuple))->usesuper)
649 elog(DEBUG, "pg_ownercheck: user \"%s\" is superuser",
652 ReleaseSysCache(tuple);
656 ReleaseSysCache(tuple);
657 /* caution: usename is inaccessible beyond this point... */
659 tuple = SearchSysCache(PROCNAME,
660 PointerGetDatum(funcname),
661 Int32GetDatum(nargs),
662 PointerGetDatum(arglist),
664 if (!HeapTupleIsValid(tuple))
665 func_error("pg_func_ownercheck", funcname, nargs, arglist, NULL);
667 owner_id = ((Form_pg_proc) GETSTRUCT(tuple))->proowner;
669 ReleaseSysCache(tuple);
671 return userid == owner_id;
675 * Ownership check for an aggregate function (specified by name and
679 pg_aggr_ownercheck(Oid userid,
687 tuple = SearchSysCache(SHADOWSYSID,
688 PointerGetDatum(userid),
690 if (!HeapTupleIsValid(tuple))
691 elog(ERROR, "pg_aggr_ownercheck: invalid user id %u",
693 usename = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
696 * Superusers bypass all permission-checking.
698 if (((Form_pg_shadow) GETSTRUCT(tuple))->usesuper)
701 elog(DEBUG, "pg_aggr_ownercheck: user \"%s\" is superuser",
704 ReleaseSysCache(tuple);
708 ReleaseSysCache(tuple);
709 /* caution: usename is inaccessible beyond this point... */
711 tuple = SearchSysCache(AGGNAME,
712 PointerGetDatum(aggname),
713 ObjectIdGetDatum(basetypeID),
715 if (!HeapTupleIsValid(tuple))
716 agg_error("pg_aggr_ownercheck", aggname, basetypeID);
718 owner_id = ((Form_pg_aggregate) GETSTRUCT(tuple))->aggowner;
720 ReleaseSysCache(tuple);
722 return userid == owner_id;