diff options
Diffstat (limited to 'src/backend/utils/adt/acl.c')
-rw-r--r-- | src/backend/utils/adt/acl.c | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c new file mode 100644 index 00000000000..289b4575c7b --- /dev/null +++ b/src/backend/utils/adt/acl.c @@ -0,0 +1,618 @@ +/*------------------------------------------------------------------------- + * + * acl.c-- + * Basic access control list data structures manipulation routines. + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/utils/adt/acl.c,v 1.1.1.1 1996/07/09 06:22:02 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ +#include <ctype.h> +#include <string.h> +#include "postgres.h" +#include "c.h" +#include "utils/acl.h" +#include "access/htup.h" +#include "catalog/pg_user.h" +#include "utils/syscache.h" +#include "utils/elog.h" +#include "utils/palloc.h" + +static char *getid(char *s, char *n); +static int32 aclitemeq(AclItem *a1, AclItem *a2); +static int32 aclitemgt(AclItem *a1, AclItem *a2); + +#define ACL_IDTYPE_GID_KEYWORD "group" +#define ACL_IDTYPE_UID_KEYWORD "user" + + +/* + * getid + * Consumes the first alphanumeric string (identifier) found in string + * 's', ignoring any leading white space. + * + * RETURNS: + * the string position in 's' that points to the next non-space character + * in 's'. Also: + * - loads the identifier into 'name'. (If no identifier is found, 'name' + * contains an empty string). + */ +static char * +getid(char *s, char *n) +{ + unsigned len; + char *id; + + Assert(s && n); + + while (isspace(*s)) + ++s; + for (id = s, len = 0; isalnum(*s); ++len, ++s) + ; + if (len > sizeof(NameData)) + elog(WARN, "getid: identifier cannot be >%d characters", + sizeof(NameData)); + if (len > 0) + memmove(n, id, len); + n[len] = '\0'; + while (isspace(*s)) + ++s; + return(s); +} + +/* + * aclparse + * Consumes and parses an ACL specification of the form: + * [group|user] [A-Za-z0-9]*[+-=][rwaR]* + * from string 's', ignoring any leading white space or white space + * between the optional id type keyword (group|user) and the actual + * ACL specification. + * + * This routine is called by the parser as well as aclitemin(), hence + * the added generality. + * + * RETURNS: + * the string position in 's' immediately following the ACL + * specification. Also: + * - loads the structure pointed to by 'aip' with the appropriate + * UID/GID, id type identifier and mode type values. + * - loads 'modechg' with the mode change flag. + */ +char * +aclparse(char *s, AclItem *aip, unsigned *modechg) +{ + HeapTuple htp; + char name[NAMEDATALEN+1]; + extern AclId get_grosysid(); + + Assert(s && aip && modechg); + + aip->ai_idtype = ACL_IDTYPE_UID; + s = getid(s, name); + if (*s != ACL_MODECHG_ADD_CHR && + *s != ACL_MODECHG_DEL_CHR && + *s != ACL_MODECHG_EQL_CHR) + { /* we just read a keyword, not a name */ + if (!strcmp(name, ACL_IDTYPE_GID_KEYWORD)) { + aip->ai_idtype = ACL_IDTYPE_GID; + } else if (strcmp(name, ACL_IDTYPE_UID_KEYWORD)) { + elog(WARN, "aclparse: bad keyword, must be [group|user]"); + } + s = getid(s, name); /* move s to the name beyond the keyword */ + if (name[0] == '\0') + elog(WARN, "aclparse: a name must follow the [group|user] keyword"); + } + if (name[0] == '\0') + aip->ai_idtype = ACL_IDTYPE_WORLD; + + switch (*s) { + case ACL_MODECHG_ADD_CHR: *modechg = ACL_MODECHG_ADD; break; + case ACL_MODECHG_DEL_CHR: *modechg = ACL_MODECHG_DEL; break; + case ACL_MODECHG_EQL_CHR: *modechg = ACL_MODECHG_EQL; break; + default: elog(WARN, "aclparse: mode change flag must use \"%s\"", + ACL_MODECHG_STR); + } + + aip->ai_mode = ACL_NO; + while (isalpha(*++s)) { + switch (*s) { + case ACL_MODE_AP_CHR: aip->ai_mode |= ACL_AP; break; + case ACL_MODE_RD_CHR: aip->ai_mode |= ACL_RD; break; + case ACL_MODE_WR_CHR: aip->ai_mode |= ACL_WR; break; + case ACL_MODE_RU_CHR: aip->ai_mode |= ACL_RU; break; + default: elog(WARN, "aclparse: mode flags must use \"%s\"", + ACL_MODE_STR); + } + } + + switch (aip->ai_idtype) { + case ACL_IDTYPE_UID: + htp = SearchSysCacheTuple(USENAME, PointerGetDatum(name), + 0,0,0); + if (!HeapTupleIsValid(htp)) + elog(WARN, "aclparse: non-existent user \"%s\"", name); + aip->ai_id = ((Form_pg_user) GETSTRUCT(htp))->usesysid; + break; + case ACL_IDTYPE_GID: + aip->ai_id = get_grosysid(name); + break; + case ACL_IDTYPE_WORLD: + aip->ai_id = ACL_ID_WORLD; + break; + } + +#ifdef ACLDEBUG_TRACE + elog(DEBUG, "aclparse: correctly read [%x %d %x], modechg=%x", + aip->ai_idtype, aip->ai_id, aip->ai_mode, *modechg); +#endif + return(s); +} + +/* + * makeacl + * Allocates storage for a new Acl with 'n' entries. + * + * RETURNS: + * the new Acl + */ +Acl * +makeacl(int n) +{ + Acl *new_acl; + Size size; + + if (n < 0) + elog(WARN, "makeacl: invalid size: %d\n", n); + size = ACL_N_SIZE(n); + if (!(new_acl = (Acl *) palloc(size))) + elog(WARN, "makeacl: palloc failed on %d\n", size); + memset((char *) new_acl, 0, size); + new_acl->size = size; + new_acl->ndim = 1; + new_acl->flags = 0; + ARR_LBOUND(new_acl)[0] = 0; + ARR_DIMS(new_acl)[0] = n; + return(new_acl); +} + +/* + * aclitemin + * Allocates storage for, and fills in, a new AclItem given a string + * 's' that contains an ACL specification. See aclparse for details. + * + * RETURNS: + * the new AclItem + */ +AclItem * +aclitemin(char *s) +{ + unsigned modechg; + AclItem *aip; + + if (!s) + elog(WARN, "aclitemin: null string"); + + aip = (AclItem *) palloc(sizeof(AclItem)); + if (!aip) + elog(WARN, "aclitemin: palloc failed"); + s = aclparse(s, aip, &modechg); + if (modechg != ACL_MODECHG_EQL) + elog(WARN, "aclitemin: cannot accept anything but = ACLs"); + while (isspace(*s)) + ++s; + if (*s) + elog(WARN, "aclitemin: extra garbage at end of specification"); + return(aip); +} + +/* + * aclitemout + * Allocates storage for, and fills in, a new null-delimited string + * containing a formatted ACL specification. See aclparse for details. + * + * RETURNS: + * the new string + */ +char * +aclitemout(AclItem *aip) +{ + register char *p; + char *out; + HeapTuple htp; + unsigned i; + static AclItem default_aclitem = { ACL_ID_WORLD, + ACL_IDTYPE_WORLD, + ACL_WORLD_DEFAULT }; + extern char *int2out(); + char *tmpname; + + if (!aip) + aip = &default_aclitem; + + p = out = palloc(strlen("group =arwR ") + 1 + NAMEDATALEN); + if (!out) + elog(WARN, "aclitemout: palloc failed"); + *p = '\0'; + + switch (aip->ai_idtype) { + case ACL_IDTYPE_UID: + htp = SearchSysCacheTuple(USESYSID, ObjectIdGetDatum(aip->ai_id), + 0,0,0); + if (!HeapTupleIsValid(htp)) { + char *tmp = int2out(aip->ai_id); + + elog(NOTICE, "aclitemout: usesysid %d not found", + aip->ai_id); + (void) strcat(p, tmp); + pfree(tmp); + } else + (void) strncat(p, (char *) &((Form_pg_user) + GETSTRUCT(htp))->usename, + sizeof(NameData)); + break; + case ACL_IDTYPE_GID: + (void) strcat(p, "group "); + tmpname = get_groname(aip->ai_id); + (void) strncat(p, tmpname, NAMEDATALEN); + pfree(tmpname); + break; + case ACL_IDTYPE_WORLD: + break; + default: + elog(WARN, "aclitemout: bad ai_idtype: %d", aip->ai_idtype); + break; + } + while (*p) + ++p; + *p++ = '='; + for (i = 0; i < N_ACL_MODES; ++i) + if ((aip->ai_mode >> i) & 01) + *p++ = ACL_MODE_STR[i]; + *p = '\0'; + + return(out); +} + +/* + * aclitemeq + * aclitemgt + * AclItem equality and greater-than comparison routines. + * Two AclItems are equal iff they are both NULL or they have the + * same identifier (and identifier type). + * + * RETURNS: + * a boolean value indicating = or > + */ +static int32 +aclitemeq(AclItem *a1, AclItem *a2) +{ + if (!a1 && !a2) + return(1); + if (!a1 || !a2) + return(0); + return(a1->ai_idtype == a2->ai_idtype && a1->ai_id == a2->ai_id); +} + +static int32 +aclitemgt(AclItem *a1, AclItem *a2) +{ + if (a1 && !a2) + return(1); + if (!a1 || !a2) + return(0); + return((a1->ai_idtype > a2->ai_idtype) || + (a1->ai_idtype == a2->ai_idtype && a1->ai_id > a2->ai_id)); +} + +Acl * +aclownerdefault(AclId ownerid) +{ + Acl *acl; + AclItem *aip; + + acl = makeacl(2); + aip = ACL_DAT(acl); + aip[0].ai_idtype = ACL_IDTYPE_WORLD; + aip[0].ai_id = ACL_ID_WORLD; + aip[0].ai_mode = ACL_WORLD_DEFAULT; + aip[1].ai_idtype = ACL_IDTYPE_UID; + aip[1].ai_id = ownerid; + aip[1].ai_mode = ACL_OWNER_DEFAULT; + return(acl); +} + +Acl * +acldefault() +{ + Acl *acl; + AclItem *aip; + + acl = makeacl(1); + aip = ACL_DAT(acl); + aip[0].ai_idtype = ACL_IDTYPE_WORLD; + aip[0].ai_id = ACL_ID_WORLD; + aip[0].ai_mode = ACL_WORLD_DEFAULT; + return(acl); +} + +Acl * +aclinsert3(Acl *old_acl, AclItem *mod_aip, unsigned modechg) +{ + Acl *new_acl; + AclItem *old_aip, *new_aip; + unsigned src, dst, num; + + if (!old_acl || ACL_NUM(old_acl) < 1) { + new_acl = makeacl(0); + return(new_acl); + } + if (!mod_aip) { + new_acl = makeacl(ACL_NUM(old_acl)); + memmove((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl)); + return(new_acl); + } + + num = ACL_NUM(old_acl); + old_aip = ACL_DAT(old_acl); + + /* + * Search the ACL for an existing entry for 'id'. If one exists, + * just modify the entry in-place (well, in the same position, since + * we actually return a copy); otherwise, insert the new entry in + * sort-order. + */ + /* find the first element not less than the element to be inserted */ + for (dst = 0; dst < num && aclitemgt(mod_aip, old_aip+dst); ++dst) + ; + if (dst < num && aclitemeq(mod_aip, old_aip+dst)) { + /* modify in-place */ + new_acl = makeacl(ACL_NUM(old_acl)); + memmove((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl)); + new_aip = ACL_DAT(new_acl); + src = dst; + } else { + new_acl = makeacl(num + 1); + new_aip = ACL_DAT(new_acl); + if (dst == 0) { /* start */ + elog(WARN, "aclinsert3: insertion before world ACL??"); + } else if (dst >= num) { /* end */ + memmove((char *) new_aip, + (char *) old_aip, + num * sizeof(AclItem)); + } else { /* middle */ + memmove((char *) new_aip, + (char *) old_aip, + dst * sizeof(AclItem)); + memmove((char *) (new_aip+dst+1), + (char *) (old_aip+dst), + (num - dst) * sizeof(AclItem)); + } + new_aip[dst].ai_id = mod_aip->ai_id; + new_aip[dst].ai_idtype = mod_aip->ai_idtype; + num++; /* set num to the size of new_acl */ + src = 0; /* world entry */ + } + switch (modechg) { + case ACL_MODECHG_ADD: new_aip[dst].ai_mode = + old_aip[src].ai_mode | mod_aip->ai_mode; + break; + case ACL_MODECHG_DEL: new_aip[dst].ai_mode = + old_aip[src].ai_mode & ~mod_aip->ai_mode; + break; + case ACL_MODECHG_EQL: new_aip[dst].ai_mode = + mod_aip->ai_mode; + break; + } + /* if the newly added entry has no permissions, delete it from + the list. For example, this helps in removing entries for users who + no longer exists...*/ + for (dst = 1; dst < num; dst++) { + if (new_aip[dst].ai_mode == 0) { + int i; + for (i=dst+1; i<num; i++) { + new_aip[i-1].ai_id = new_aip[i].ai_id; + new_aip[i-1].ai_idtype = new_aip[i].ai_idtype; + new_aip[i-1].ai_mode = new_aip[i].ai_mode; + } + ARR_DIMS(new_acl)[0] = num -1 ; + break; + } + } + + return(new_acl); +} + +/* + * aclinsert + * + */ +Acl * +aclinsert(Acl *old_acl, AclItem *mod_aip) +{ + return(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL)); +} + +Acl * +aclremove(Acl *old_acl, AclItem *mod_aip) +{ + Acl *new_acl; + AclItem *old_aip, *new_aip; + unsigned dst, old_num, new_num; + + if (!old_acl || ACL_NUM(old_acl) < 1) { + new_acl = makeacl(0); + return(new_acl); + } + if (!mod_aip) { + new_acl = makeacl(ACL_NUM(old_acl)); + memmove((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl)); + return(new_acl); + } + + old_num = ACL_NUM(old_acl); + old_aip = ACL_DAT(old_acl); + + for (dst = 0; dst < old_num && !aclitemeq(mod_aip, old_aip+dst); ++dst) + ; + if (dst >= old_num) { /* not found or empty */ + new_acl = makeacl(ACL_NUM(old_acl)); + memmove((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl)); + } else { + new_num = old_num - 1; + new_acl = makeacl(ACL_NUM(old_acl) - 1); + new_aip = ACL_DAT(new_acl); + if (dst == 0) { /* start */ + elog(WARN, "aclremove: removal of the world ACL??"); + } else if (dst == old_num - 1) {/* end */ + memmove((char *) new_aip, + (char *) old_aip, + new_num * sizeof(AclItem)); + } else { /* middle */ + memmove((char *) new_aip, + (char *) old_aip, + dst * sizeof(AclItem)); + memmove((char *) (new_aip+dst), + (char *) (old_aip+dst+1), + (new_num - dst) * sizeof(AclItem)); + } + } + return(new_acl); +} + +int32 +aclcontains(Acl *acl, AclItem *aip) +{ + unsigned i, num; + AclItem *aidat; + + if (!acl || !aip || ((num = ACL_NUM(acl)) < 1)) + return(0); + aidat = ACL_DAT(acl); + for (i = 0; i < num; ++i) + if (aclitemeq(aip, aidat+i)) + return(1); + return(0); +} + +/* parser support routines */ + +/* + * aclmakepriv + * make a acl privilege string out of an existing privilege string + * and a new privilege + * + * does not add duplicate privileges + * + * the CALLER is reponsible for free'ing the string returned + */ + +char* +aclmakepriv(char* old_privlist, char new_priv) +{ + char* priv; + int i; + int l; + + Assert(strlen(old_privlist)<5); + priv = malloc(5); /* at most "rwaR" */; + + if (old_privlist == NULL || old_privlist[0] == '\0') { + priv[0] = new_priv; + priv[1] = '\0'; + return priv; + } + + strcpy(priv,old_privlist); + + l = strlen(old_privlist); + + if (l == 4) { /* can't add any more privileges */ + return priv; + } + + /* check to see if the new privilege is already in the old string */ + for (i=0;i<l;i++) { + if (priv[i] == new_priv) + break; + } + if (i == l) { /* we really have a new privilege*/ + priv[l] = new_priv; + priv[l+1] = '\0'; + } + + return priv; +} + +/* + * aclmakeuser + * user_type must be "A" - all users + * "G" - group + * "U" - user + * + * concatentates the two strings together with a space in between + * + * this routine is used in the parser + * + * the CALLER is responsible for freeing the memory allocated + */ + +char* +aclmakeuser(char* user_type, char* user) +{ + char* user_list; + + user_list = malloc(strlen(user) + 3); + sprintf(user_list, "%s %s", user_type, user); + return user_list; +} + + +/* + * makeAclStmt: + * this is a helper routine called by the parser + * create a ChangeAclStmt + * we take in the privilegs, relation_name_list, and grantee + * as well as a single character '+' or '-' to indicate grant or revoke + * + * returns a new ChangeACLStmt* + * + * this routines works by creating a old-style changle acl string and + * then calling aclparse; + */ + +ChangeACLStmt* +makeAclStmt(char* privileges, List* rel_list, char* grantee, + char grant_or_revoke) +{ + ChangeACLStmt *n = makeNode(ChangeACLStmt); + char str[MAX_PARSE_BUFFER]; + + n->aclitem = (AclItem*)palloc(sizeof(AclItem)); + /* the grantee string is "G <group_name>", "U <user_name>", or "ALL" */ + if (grantee[0] == 'G') /* group permissions */ + { + sprintf(str,"%s %s%c%s", + ACL_IDTYPE_GID_KEYWORD, + grantee+2, grant_or_revoke,privileges); + } + else if (grantee[0] == 'U') /* user permission */ + { + sprintf(str,"%s %s%c%s", + ACL_IDTYPE_UID_KEYWORD, + grantee+2, grant_or_revoke,privileges); + } + else /* all permission */ + { + sprintf(str,"%c%s", + grant_or_revoke,privileges); + } + n->relNames = rel_list; + aclparse(str, n->aclitem, (unsigned*)&n->modechg); + return n; +} + + |