mirror of
https://github.com/postgres/postgres.git
synced 2025-04-24 10:47:04 +03:00
617 lines
15 KiB
C
617 lines
15 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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.4 1996/11/06 06:49:34 scrappy Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include "postgres.h"
|
|
#include "utils/acl.h"
|
|
#include "catalog/pg_user.h"
|
|
#include "utils/syscache.h"
|
|
#include "utils/palloc.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);
|
|
|
|
#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;
|
|
}
|
|
|
|
|