/*------------------------------------------------------------------------- * * 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.15 1997/09/08 02:30:15 momjian Exp $ * *------------------------------------------------------------------------- */ #include #include #include "postgres.h" #include #include "utils/acl.h" #include "catalog/pg_user.h" #include "utils/syscache.h" #include "miscadmin.h" static char *getid(char *s, char *n); static int32 aclitemeq(AclItem * a1, AclItem * a2); static int32 aclitemgt(AclItem * a1, AclItem * a2); static char *aclparse(char *s, AclItem * aip, unsigned *modechg); #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) || *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. */ static char * aclparse(char *s, AclItem * aip, unsigned *modechg) { HeapTuple htp; char name[NAMEDATALEN]; 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); strcat(p, tmp); pfree(tmp); } else strncat(p, (char *) &((Form_pg_user) GETSTRUCT(htp))->usename, sizeof(NameData)); break; case ACL_IDTYPE_GID: strcat(p, "group "); tmpname = get_groname(aip->ai_id); strncat(p, tmpname, NAMEDATALEN); 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(void) { 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; /* Adjust also the array size because it is used for memmove */ ARR_SIZE(new_acl) -= sizeof(AclItem); 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 ", "U ", 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; }