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