]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/acl.c
9b150d387f9295d06cb9186f3ba5abeeee0a7200
[postgresql] / src / backend / utils / adt / acl.c
1 /*-------------------------------------------------------------------------
2  *
3  * acl.c
4  *        Basic access control list data structures manipulation routines.
5  *
6  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $Header: /cvsroot/pgsql/src/backend/utils/adt/acl.c,v 1.63 2001/06/12 16:34:26 momjian Exp $
12  *
13  *-------------------------------------------------------------------------
14  */
15 #include "postgres.h"
16
17 #include <ctype.h>
18
19 #include "access/heapam.h"
20 #include "catalog/catalog.h"
21 #include "catalog/pg_shadow.h"
22 #include "catalog/pg_type.h"
23 #include "lib/stringinfo.h"
24 #include "miscadmin.h"
25 #include "utils/acl.h"
26 #include "utils/builtins.h"
27 #include "utils/memutils.h"
28 #include "utils/syscache.h"
29
30 static const char *getid(const char *s, char *n);
31 static bool aclitemeq(const AclItem *a1, const AclItem *a2);
32 static bool aclitemgt(const AclItem *a1, const AclItem *a2);
33
34 #define ACL_IDTYPE_GID_KEYWORD  "group"
35 #define ACL_IDTYPE_UID_KEYWORD  "user"
36
37 /*
38  * getid
39  *              Consumes the first alphanumeric string (identifier) found in string
40  *              's', ignoring any leading white space.  If it finds a double quote
41  *              it returns the word inside the quotes.
42  *
43  * RETURNS:
44  *              the string position in 's' that points to the next non-space character
45  *              in 's', after any quotes.  Also:
46  *              - loads the identifier into 'name'.  (If no identifier is found, 'name'
47  *                contains an empty string.)  name must be NAMEDATALEN bytes.
48  */
49 static const char *
50 getid(const char *s, char *n)
51 {
52         unsigned        len;
53         const char *id;
54         int                     in_quotes = 0;
55
56         Assert(s && n);
57
58         while (isspace((unsigned char) *s))
59                 ++s;
60
61         if (*s == '"')
62         {
63                 in_quotes = 1;
64                 s++;
65         }
66
67         for (id = s, len = 0;
68                  isalnum((unsigned char) *s) || *s == '_' || in_quotes;
69                  ++len, ++s)
70         {
71                 if (in_quotes && *s == '"')
72                 {
73                         len--;
74                         in_quotes = 0;
75                 }
76         }
77         if (len >= NAMEDATALEN)
78                 elog(ERROR, "getid: identifier must be <%d characters",
79                          NAMEDATALEN);
80         if (len > 0)
81                 memmove(n, id, len);
82         n[len] = '\0';
83         while (isspace((unsigned char) *s))
84                 ++s;
85         return s;
86 }
87
88 /*
89  * aclparse
90  *              Consumes and parses an ACL specification of the form:
91  *                              [group|user] [A-Za-z0-9]*[+-=][rwaR]*
92  *              from string 's', ignoring any leading white space or white space
93  *              between the optional id type keyword (group|user) and the actual
94  *              ACL specification.
95  *
96  *              This routine is called by the parser as well as aclitemin(), hence
97  *              the added generality.
98  *
99  * RETURNS:
100  *              the string position in 's' immediately following the ACL
101  *              specification.  Also:
102  *              - loads the structure pointed to by 'aip' with the appropriate
103  *                UID/GID, id type identifier and mode type values.
104  *              - loads 'modechg' with the mode change flag.
105  */
106 const char *
107 aclparse(const char *s, AclItem *aip, unsigned *modechg)
108 {
109         HeapTuple       htup;
110         char            name[NAMEDATALEN];
111
112         Assert(s && aip && modechg);
113
114 #ifdef ACLDEBUG
115         elog(DEBUG, "aclparse: input = '%s'", s);
116 #endif
117         aip->ai_idtype = ACL_IDTYPE_UID;
118         s = getid(s, name);
119         if (*s != ACL_MODECHG_ADD_CHR &&
120                 *s != ACL_MODECHG_DEL_CHR &&
121                 *s != ACL_MODECHG_EQL_CHR)
122         {
123                 /* we just read a keyword, not a name */
124                 if (!strcmp(name, ACL_IDTYPE_GID_KEYWORD))
125                         aip->ai_idtype = ACL_IDTYPE_GID;
126                 else if (strcmp(name, ACL_IDTYPE_UID_KEYWORD))
127                         elog(ERROR, "aclparse: bad keyword, must be [group|user]");
128                 s = getid(s, name);             /* move s to the name beyond the keyword */
129                 if (name[0] == '\0')
130                         elog(ERROR, "aclparse: a name must follow the [group|user] keyword");
131         }
132         if (name[0] == '\0')
133                 aip->ai_idtype = ACL_IDTYPE_WORLD;
134
135         switch (*s)
136         {
137                 case ACL_MODECHG_ADD_CHR:
138                         *modechg = ACL_MODECHG_ADD;
139                         break;
140                 case ACL_MODECHG_DEL_CHR:
141                         *modechg = ACL_MODECHG_DEL;
142                         break;
143                 case ACL_MODECHG_EQL_CHR:
144                         *modechg = ACL_MODECHG_EQL;
145                         break;
146                 default:
147                         elog(ERROR, "aclparse: mode change flag must use \"%s\"",
148                                  ACL_MODECHG_STR);
149         }
150
151         aip->ai_mode = ACL_NO;
152         while (isalpha((unsigned char) *++s))
153         {
154                 switch (*s)
155                 {
156                         case ACL_MODE_INSERT_CHR:
157                                 aip->ai_mode |= ACL_INSERT;
158                                 break;
159                         case ACL_MODE_SELECT_CHR:
160                                 aip->ai_mode |= ACL_SELECT;
161                                 break;
162                         case ACL_MODE_UPDATE_CHR:
163                                 aip->ai_mode |= ACL_UPDATE;
164                                 break;
165                         case ACL_MODE_DELETE_CHR:
166                                 aip->ai_mode |= ACL_DELETE;
167                                 break;
168                         case ACL_MODE_RULE_CHR:
169                                 aip->ai_mode |= ACL_RULE;
170                                 break;
171                         case ACL_MODE_REFERENCES_CHR:
172                                 aip->ai_mode |= ACL_REFERENCES;
173                                 break;
174                         case ACL_MODE_TRIGGER_CHR:
175                                 aip->ai_mode |= ACL_TRIGGER;
176                                 break;
177                         default:
178                                 elog(ERROR, "aclparse: mode flags must use \"%s\"",
179                                          ACL_MODE_STR);
180                 }
181         }
182
183         switch (aip->ai_idtype)
184         {
185                 case ACL_IDTYPE_UID:
186                         htup = SearchSysCache(SHADOWNAME,
187                                                                   PointerGetDatum(name),
188                                                                   0, 0, 0);
189                         if (!HeapTupleIsValid(htup))
190                                 elog(ERROR, "aclparse: non-existent user \"%s\"", name);
191                         aip->ai_id = ((Form_pg_shadow) GETSTRUCT(htup))->usesysid;
192                         ReleaseSysCache(htup);
193                         break;
194                 case ACL_IDTYPE_GID:
195                         aip->ai_id = get_grosysid(name);
196                         break;
197                 case ACL_IDTYPE_WORLD:
198                         aip->ai_id = ACL_ID_WORLD;
199                         break;
200         }
201
202 #ifdef ACLDEBUG
203         elog(DEBUG, "aclparse: correctly read [%x %d %x], modechg=%x",
204                  aip->ai_idtype, aip->ai_id, aip->ai_mode, *modechg);
205 #endif
206         return s;
207 }
208
209 /*
210  * makeacl
211  *              Allocates storage for a new Acl with 'n' entries.
212  *
213  * RETURNS:
214  *              the new Acl
215  */
216 Acl *
217 makeacl(int n)
218 {
219         Acl                *new_acl;
220         Size            size;
221
222         if (n < 0)
223                 elog(ERROR, "makeacl: invalid size: %d", n);
224         size = ACL_N_SIZE(n);
225         new_acl = (Acl *) palloc(size);
226         MemSet((char *) new_acl, 0, size);
227         new_acl->size = size;
228         new_acl->ndim = 1;
229         new_acl->flags = 0;
230         ARR_LBOUND(new_acl)[0] = 0;
231         ARR_DIMS(new_acl)[0] = n;
232         return new_acl;
233 }
234
235 /*
236  * aclitemin
237  *              Allocates storage for, and fills in, a new AclItem given a string
238  *              's' that contains an ACL specification.  See aclparse for details.
239  *
240  * RETURNS:
241  *              the new AclItem
242  */
243 Datum
244 aclitemin(PG_FUNCTION_ARGS)
245 {
246         const char *s = PG_GETARG_CSTRING(0);
247         AclItem    *aip;
248         unsigned        modechg;
249
250         aip = (AclItem *) palloc(sizeof(AclItem));
251         s = aclparse(s, aip, &modechg);
252         if (modechg != ACL_MODECHG_EQL)
253                 elog(ERROR, "aclitemin: cannot accept anything but = ACLs");
254         while (isspace((unsigned char) *s))
255                 ++s;
256         if (*s)
257                 elog(ERROR, "aclitemin: extra garbage at end of specification");
258         PG_RETURN_ACLITEM_P(aip);
259 }
260
261 /*
262  * aclitemout
263  *              Allocates storage for, and fills in, a new null-delimited string
264  *              containing a formatted ACL specification.  See aclparse for details.
265  *
266  * RETURNS:
267  *              the new string
268  */
269 Datum
270 aclitemout(PG_FUNCTION_ARGS)
271 {
272         AclItem    *aip = PG_GETARG_ACLITEM_P(0);
273         char       *p;
274         char       *out;
275         HeapTuple       htup;
276         unsigned        i;
277         char       *tmpname;
278
279         p = out = palloc(strlen("group =" ACL_MODE_STR " ") + 1 + NAMEDATALEN);
280         *p = '\0';
281
282         switch (aip->ai_idtype)
283         {
284                 case ACL_IDTYPE_UID:
285                         htup = SearchSysCache(SHADOWSYSID,
286                                                                   ObjectIdGetDatum(aip->ai_id),
287                                                                   0, 0, 0);
288                         if (HeapTupleIsValid(htup))
289                         {
290                                 strncat(p,
291                                         NameStr(((Form_pg_shadow) GETSTRUCT(htup))->usename),
292                                                 NAMEDATALEN);
293                                 ReleaseSysCache(htup);
294                         }
295                         else
296                         {
297                                 /* Generate numeric UID if we don't find an entry */
298                                 char       *tmp;
299
300                                 tmp = DatumGetCString(DirectFunctionCall1(int4out,
301                                                                          Int32GetDatum((int32) aip->ai_id)));
302                                 strcat(p, tmp);
303                                 pfree(tmp);
304                         }
305                         break;
306                 case ACL_IDTYPE_GID:
307                         strcat(p, "group ");
308                         tmpname = get_groname(aip->ai_id);
309                         if (tmpname != NULL)
310                                 strncat(p, tmpname, NAMEDATALEN);
311                         else
312                         {
313                                 /* Generate numeric GID if we don't find an entry */
314                                 char       *tmp;
315
316                                 tmp = DatumGetCString(DirectFunctionCall1(int4out,
317                                                                          Int32GetDatum((int32) aip->ai_id)));
318                                 strcat(p, tmp);
319                                 pfree(tmp);
320                         }
321                         break;
322                 case ACL_IDTYPE_WORLD:
323                         break;
324                 default:
325                         elog(ERROR, "aclitemout: bad ai_idtype: %d", aip->ai_idtype);
326                         break;
327         }
328         while (*p)
329                 ++p;
330         *p++ = '=';
331         for (i = 0; i < N_ACL_MODES; ++i)
332                 if ((aip->ai_mode >> i) & 01)
333                         *p++ = ACL_MODE_STR[i];
334         *p = '\0';
335
336         PG_RETURN_CSTRING(out);
337 }
338
339 /*
340  * aclitemeq
341  * aclitemgt
342  *              AclItem equality and greater-than comparison routines.
343  *              Two AclItems are considered equal iff they have the
344  *              same identifier (and identifier type); the mode is ignored.
345  *              Note that these routines are really only useful for sorting
346  *              AclItems into identifier order.
347  *
348  * RETURNS:
349  *              a boolean value indicating = or >
350  */
351 static bool
352 aclitemeq(const AclItem *a1, const AclItem *a2)
353 {
354         return a1->ai_idtype == a2->ai_idtype && a1->ai_id == a2->ai_id;
355 }
356
357 static bool
358 aclitemgt(const AclItem *a1, const AclItem *a2)
359 {
360         return ((a1->ai_idtype > a2->ai_idtype) ||
361                         (a1->ai_idtype == a2->ai_idtype && a1->ai_id > a2->ai_id));
362 }
363
364
365 /*
366  * acldefault()  --- create an ACL describing default access permissions
367  *
368  * Change this routine if you want to alter the default access policy for
369  * newly-created tables (or any table with a NULL acl entry in pg_class)
370  */
371 Acl *
372 acldefault(const char *relname, AclId ownerid)
373 {
374         Acl                *acl;
375         AclItem    *aip;
376
377 #define ACL_WORLD_DEFAULT               (ACL_NO)
378 #define ACL_OWNER_DEFAULT               (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_RULE|ACL_REFERENCES|ACL_TRIGGER)
379
380         acl = makeacl(2);
381         aip = ACL_DAT(acl);
382         aip[0].ai_idtype = ACL_IDTYPE_WORLD;
383         aip[0].ai_id = ACL_ID_WORLD;
384         aip[0].ai_mode = IsSystemRelationName(relname) ? ACL_SELECT : ACL_WORLD_DEFAULT;
385         aip[1].ai_idtype = ACL_IDTYPE_UID;
386         aip[1].ai_id = ownerid;
387         aip[1].ai_mode = ACL_OWNER_DEFAULT;
388         return acl;
389 }
390
391
392 /*
393  * Add or replace an item in an ACL array.  The result is a modified copy;
394  * the input object is not changed.
395  *
396  * NB: caller is responsible for having detoasted the input ACL, if needed.
397  */
398 Acl *
399 aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg)
400 {
401         Acl                *new_acl;
402         AclItem    *old_aip,
403                            *new_aip;
404         int                     dst,
405                                 num;
406
407         /* These checks for null input are probably dead code, but... */
408         if (!old_acl || ACL_NUM(old_acl) < 1)
409                 old_acl = makeacl(1);
410         if (!mod_aip)
411         {
412                 new_acl = makeacl(ACL_NUM(old_acl));
413                 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
414                 return new_acl;
415         }
416
417         num = ACL_NUM(old_acl);
418         old_aip = ACL_DAT(old_acl);
419
420         /*
421          * Search the ACL for an existing entry for 'id'.  If one exists, just
422          * modify the entry in-place (well, in the same position, since we
423          * actually return a copy); otherwise, insert the new entry in
424          * sort-order.
425          */
426         /* find the first element not less than the element to be inserted */
427         for (dst = 0; dst < num && aclitemgt(mod_aip, old_aip + dst); ++dst)
428                 ;
429
430         if (dst < num && aclitemeq(mod_aip, old_aip + dst))
431         {
432                 /* found a match, so modify existing item */
433                 new_acl = makeacl(num);
434                 new_aip = ACL_DAT(new_acl);
435                 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
436         }
437         else
438         {
439                 /* need to insert a new item */
440                 new_acl = makeacl(num + 1);
441                 new_aip = ACL_DAT(new_acl);
442                 if (dst == 0)
443                 {                                               /* start */
444                         elog(ERROR, "aclinsert3: insertion before world ACL??");
445                 }
446                 else if (dst >= num)
447                 {                                               /* end */
448                         memcpy((char *) new_aip,
449                                    (char *) old_aip,
450                                    num * sizeof(AclItem));
451                 }
452                 else
453                 {                                               /* middle */
454                         memcpy((char *) new_aip,
455                                    (char *) old_aip,
456                                    dst * sizeof(AclItem));
457                         memcpy((char *) (new_aip + dst + 1),
458                                    (char *) (old_aip + dst),
459                                    (num - dst) * sizeof(AclItem));
460                 }
461                 /* initialize the new entry with no permissions */
462                 new_aip[dst].ai_id = mod_aip->ai_id;
463                 new_aip[dst].ai_idtype = mod_aip->ai_idtype;
464                 new_aip[dst].ai_mode = 0;
465                 num++;                                  /* set num to the size of new_acl */
466         }
467
468         /* apply the permissions mod */
469         switch (modechg)
470         {
471                 case ACL_MODECHG_ADD:
472                         new_aip[dst].ai_mode |= mod_aip->ai_mode;
473                         break;
474                 case ACL_MODECHG_DEL:
475                         new_aip[dst].ai_mode &= ~mod_aip->ai_mode;
476                         break;
477                 case ACL_MODECHG_EQL:
478                         new_aip[dst].ai_mode = mod_aip->ai_mode;
479                         break;
480         }
481
482         /*
483          * if the adjusted entry has no permissions, delete it from the list.
484          * For example, this helps in removing entries for users who no longer
485          * exist.  EXCEPTION: never remove the world entry.
486          */
487         if (new_aip[dst].ai_mode == 0 && dst > 0)
488         {
489                 memmove((char *) (new_aip + dst),
490                                 (char *) (new_aip + dst + 1),
491                                 (num - dst - 1) * sizeof(AclItem));
492                 ARR_DIMS(new_acl)[0] = num - 1;
493                 ARR_SIZE(new_acl) -= sizeof(AclItem);
494         }
495
496         return new_acl;
497 }
498
499 /*
500  * aclinsert (exported function)
501  */
502 Datum
503 aclinsert(PG_FUNCTION_ARGS)
504 {
505         Acl                *old_acl = PG_GETARG_ACL_P(0);
506         AclItem    *mod_aip = PG_GETARG_ACLITEM_P(1);
507
508         PG_RETURN_ACL_P(aclinsert3(old_acl, mod_aip, ACL_MODECHG_EQL));
509 }
510
511 Datum
512 aclremove(PG_FUNCTION_ARGS)
513 {
514         Acl                *old_acl = PG_GETARG_ACL_P(0);
515         AclItem    *mod_aip = PG_GETARG_ACLITEM_P(1);
516         Acl                *new_acl;
517         AclItem    *old_aip,
518                            *new_aip;
519         int                     dst,
520                                 old_num,
521                                 new_num;
522
523         /* These checks for null input should be dead code, but... */
524         if (!old_acl || ACL_NUM(old_acl) < 1)
525                 old_acl = makeacl(1);
526         if (!mod_aip)
527         {
528                 new_acl = makeacl(ACL_NUM(old_acl));
529                 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
530                 PG_RETURN_ACL_P(new_acl);
531         }
532
533         old_num = ACL_NUM(old_acl);
534         old_aip = ACL_DAT(old_acl);
535
536         /* Search for the matching entry */
537         for (dst = 0; dst < old_num && !aclitemeq(mod_aip, old_aip + dst); ++dst)
538                 ;
539
540         if (dst >= old_num)
541         {
542                 /* Not found, so return copy of source ACL */
543                 new_acl = makeacl(old_num);
544                 memcpy((char *) new_acl, (char *) old_acl, ACL_SIZE(old_acl));
545         }
546         else
547         {
548                 new_num = old_num - 1;
549                 new_acl = makeacl(new_num);
550                 new_aip = ACL_DAT(new_acl);
551                 if (dst == 0)
552                 {                                               /* start */
553                         elog(ERROR, "aclremove: removal of the world ACL??");
554                 }
555                 else if (dst == old_num - 1)
556                 {                                               /* end */
557                         memcpy((char *) new_aip,
558                                    (char *) old_aip,
559                                    new_num * sizeof(AclItem));
560                 }
561                 else
562                 {                                               /* middle */
563                         memcpy((char *) new_aip,
564                                    (char *) old_aip,
565                                    dst * sizeof(AclItem));
566                         memcpy((char *) (new_aip + dst),
567                                    (char *) (old_aip + dst + 1),
568                                    (new_num - dst) * sizeof(AclItem));
569                 }
570         }
571
572         PG_RETURN_ACL_P(new_acl);
573 }
574
575 Datum
576 aclcontains(PG_FUNCTION_ARGS)
577 {
578         Acl                *acl = PG_GETARG_ACL_P(0);
579         AclItem    *aip = PG_GETARG_ACLITEM_P(1);
580         AclItem    *aidat;
581         int                     i,
582                                 num;
583
584         num = ACL_NUM(acl);
585         aidat = ACL_DAT(acl);
586         for (i = 0; i < num; ++i)
587         {
588                 /* Note that aclitemeq only considers id, not mode */
589                 if (aclitemeq(aip, aidat + i) &&
590                         aip->ai_mode == aidat[i].ai_mode)
591                         PG_RETURN_BOOL(true);
592         }
593         PG_RETURN_BOOL(false);
594 }
595
596
597 /*
598  * Parser support routines for ACL-related statements.
599  *
600  * XXX CAUTION: these are called from gram.y, which is not allowed to
601  * do any table accesses.  Therefore, it is not kosher to do things
602  * like trying to translate usernames to user IDs here.  Keep it all
603  * in string form until statement execution time.
604  */
605
606 /*
607  * aclmakepriv
608  *        make a acl privilege string out of an existing privilege string
609  * and a new privilege
610  *
611  * does not add duplicate privileges
612  */
613 char *
614 aclmakepriv(const char *old_privlist, char new_priv)
615 {
616         char       *priv;
617         int                     i;
618         int                     l;
619
620         Assert(strlen(old_privlist) <= strlen(ACL_MODE_STR));
621         priv = palloc(strlen(ACL_MODE_STR)+1);
622
623         if (old_privlist == NULL || old_privlist[0] == '\0')
624         {
625                 priv[0] = new_priv;
626                 priv[1] = '\0';
627                 return priv;
628         }
629
630         strcpy(priv, old_privlist);
631
632         l = strlen(old_privlist);
633
634         if (l == strlen(ACL_MODE_STR))
635         {                                                       /* can't add any more privileges */
636                 return priv;
637         }
638
639         /* check to see if the new privilege is already in the old string */
640         for (i = 0; i < l; i++)
641         {
642                 if (priv[i] == new_priv)
643                         break;
644         }
645         if (i == l)
646         {                                                       /* we really have a new privilege */
647                 priv[l] = new_priv;
648                 priv[l + 1] = '\0';
649         }
650
651         return priv;
652 }
653
654 /*
655  * aclmakeuser
656  *        user_type must be "A"  - all users
657  *                                              "G"  - group
658  *                                              "U"  - user
659  *
660  * Just concatenates the two strings together with a space in between.
661  * Per above comments, we can't try to resolve a user or group name here.
662  */
663 char *
664 aclmakeuser(const char *user_type, const char *user)
665 {
666         char       *user_list;
667
668         user_list = palloc(strlen(user_type) + strlen(user) + 2);
669         sprintf(user_list, "%s %s", user_type, user);
670         return user_list;
671 }
672
673
674 /*
675  * makeAclString:  We take in the privileges and grantee as well as a
676  * single character '+' or '-' to indicate grant or revoke.
677  *
678  * We convert the information to the same external form recognized by
679  * aclitemin (see aclparse) and return that string.  Conversion to
680  * internal form happens when the statement is executed.
681  */
682 char *
683 makeAclString(const char *privileges, const char *grantee, char grant_or_revoke)
684 {
685         StringInfoData str;
686         char *ret;
687
688         initStringInfo(&str);
689
690         /* the grantee string is "G <group_name>", "U <user_name>", or "ALL" */
691         if (grantee[0] == 'G')          /* group permissions */
692         {
693                 appendStringInfo(&str, "%s \"%s\"%c%s",
694                                                  ACL_IDTYPE_GID_KEYWORD,
695                                                  grantee + 2, grant_or_revoke, privileges);
696         }
697         else if (grantee[0] == 'U') /* user permission */
698         {
699                 appendStringInfo(&str, "%s \"%s\"%c%s",
700                                                  ACL_IDTYPE_UID_KEYWORD,
701                                                  grantee + 2, grant_or_revoke, privileges);
702         }
703         else
704         {
705                 /* all permission */
706                 appendStringInfo(&str, "%c%s",
707                                                  grant_or_revoke, privileges);
708         }
709         ret = pstrdup(str.data);
710         pfree(str.data);
711         return ret;
712 }