Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * Utility routines for SQL dumping
4 : : *
5 : : * Basically this is stuff that is useful in both pg_dump and pg_dumpall.
6 : : *
7 : : *
8 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
9 : : * Portions Copyright (c) 1994, Regents of the University of California
10 : : *
11 : : * src/bin/pg_dump/dumputils.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : #include "postgres_fe.h"
16 : :
17 : : #include <ctype.h>
18 : :
19 : : #include "dumputils.h"
20 : : #include "fe_utils/string_utils.h"
21 : :
22 : :
23 : : static bool parseAclItem(const char *item, const char *type,
24 : : const char *name, const char *subname, int remoteVersion,
25 : : PQExpBuffer grantee, PQExpBuffer grantor,
26 : : PQExpBuffer privs, PQExpBuffer privswgo);
27 : : static char *dequoteAclUserName(PQExpBuffer output, char *input);
28 : : static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
29 : : const char *subname);
30 : :
31 : :
32 : : /*
33 : : * Build GRANT/REVOKE command(s) for an object.
34 : : *
35 : : * name: the object name, in the form to use in the commands (already quoted)
36 : : * subname: the sub-object name, if any (already quoted); NULL if none
37 : : * nspname: the namespace the object is in (NULL if none); not pre-quoted
38 : : * type: the object type (as seen in GRANT command: must be one of
39 : : * TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
40 : : * FOREIGN DATA WRAPPER, SERVER, PARAMETER or LARGE OBJECT)
41 : : * acls: the ACL string fetched from the database
42 : : * baseacls: the initial ACL string for this object
43 : : * owner: username of object owner (will be passed through fmtId); can be
44 : : * NULL or empty string to indicate "no owner known"
45 : : * prefix: string to prefix to each generated command; typically empty
46 : : * remoteVersion: version of database
47 : : *
48 : : * Returns true if okay, false if could not parse the acl string.
49 : : * The resulting commands (if any) are appended to the contents of 'sql'.
50 : : *
51 : : * baseacls is typically the result of acldefault() for the object's type
52 : : * and owner. However, if there is a pg_init_privs entry for the object,
53 : : * it should instead be the initprivs ACLs. When acls is itself a
54 : : * pg_init_privs entry, baseacls is what to dump that relative to; then
55 : : * it can be either an acldefault() value or an empty ACL "{}".
56 : : *
57 : : * Note: when processing a default ACL, prefix is "ALTER DEFAULT PRIVILEGES "
58 : : * or something similar, and name is an empty string.
59 : : *
60 : : * Note: beware of passing a fmtId() result directly as 'name' or 'subname',
61 : : * since this routine uses fmtId() internally.
62 : : */
63 : : bool
2239 tgl@sss.pgh.pa.us 64 :CBC 23063 : buildACLCommands(const char *name, const char *subname, const char *nspname,
65 : : const char *type, const char *acls, const char *baseacls,
66 : : const char *owner, const char *prefix, int remoteVersion,
67 : : PQExpBuffer sql)
68 : : {
3350 69 : 23063 : bool ok = true;
2930 sfrost@snowman.net 70 : 23063 : char **aclitems = NULL;
860 tgl@sss.pgh.pa.us 71 : 23063 : char **baseitems = NULL;
72 : 23063 : char **grantitems = NULL;
73 : 23063 : char **revokeitems = NULL;
2930 sfrost@snowman.net 74 : 23063 : int naclitems = 0;
860 tgl@sss.pgh.pa.us 75 : 23063 : int nbaseitems = 0;
76 : 23063 : int ngrantitems = 0;
77 : 23063 : int nrevokeitems = 0;
78 : : int i;
79 : : PQExpBuffer grantee,
80 : : grantor,
81 : : privs,
82 : : privswgo;
83 : : PQExpBuffer firstsql,
84 : : secondsql;
85 : :
86 : : /*
87 : : * If the acl was NULL (initial default state), we need do nothing. Note
88 : : * that this is distinguishable from all-privileges-revoked, which will
89 : : * look like an empty array ("{}").
90 : : */
91 [ + - + + ]: 23063 : if (acls == NULL || *acls == '\0')
7625 92 : 92 : return true; /* object has default permissions */
93 : :
94 : : /* treat empty-string owner same as NULL */
6707 95 [ + - - + ]: 22971 : if (owner && *owner == '\0')
6707 tgl@sss.pgh.pa.us 96 :UBC 0 : owner = NULL;
97 : :
98 : : /* Parse the acls array */
860 tgl@sss.pgh.pa.us 99 [ - + ]:CBC 22971 : if (!parsePGArray(acls, &aclitems, &naclitems))
100 : : {
668 peter@eisentraut.org 101 :UBC 0 : free(aclitems);
860 tgl@sss.pgh.pa.us 102 : 0 : return false;
103 : : }
104 : :
105 : : /* Parse the baseacls too */
850 tgl@sss.pgh.pa.us 106 [ - + ]:CBC 22971 : if (!parsePGArray(baseacls, &baseitems, &nbaseitems))
107 : : {
668 peter@eisentraut.org 108 :UBC 0 : free(aclitems);
109 : 0 : free(baseitems);
850 tgl@sss.pgh.pa.us 110 : 0 : return false;
111 : : }
112 : :
113 : : /*
114 : : * Compare the actual ACL with the base ACL, extracting the privileges
115 : : * that need to be granted (i.e., are in the actual ACL but not the base
116 : : * ACL) and the ones that need to be revoked (the reverse). We use plain
117 : : * string comparisons to check for matches. In principle that could be
118 : : * fooled by extraneous issues such as whitespace, but since all these
119 : : * strings are the work of aclitemout(), it should be OK in practice.
120 : : * Besides, a false mismatch will just cause the output to be a little
121 : : * more verbose than it really needed to be.
122 : : */
860 tgl@sss.pgh.pa.us 123 :CBC 22971 : grantitems = (char **) pg_malloc(naclitems * sizeof(char *));
124 [ + + ]: 47289 : for (i = 0; i < naclitems; i++)
125 : : {
126 : 24318 : bool found = false;
127 : :
128 [ + + ]: 26347 : for (int j = 0; j < nbaseitems; j++)
129 : : {
130 [ + + ]: 24459 : if (strcmp(aclitems[i], baseitems[j]) == 0)
131 : : {
132 : 22430 : found = true;
133 : 22430 : break;
134 : : }
135 : : }
136 [ + + ]: 24318 : if (!found)
137 : 1888 : grantitems[ngrantitems++] = aclitems[i];
138 : : }
139 : 22971 : revokeitems = (char **) pg_malloc(nbaseitems * sizeof(char *));
140 [ + + ]: 45767 : for (i = 0; i < nbaseitems; i++)
141 : : {
142 : 22796 : bool found = false;
143 : :
144 [ + + ]: 24100 : for (int j = 0; j < naclitems; j++)
145 : : {
146 [ + + ]: 23734 : if (strcmp(baseitems[i], aclitems[j]) == 0)
147 : : {
148 : 22430 : found = true;
149 : 22430 : break;
150 : : }
151 : : }
152 [ + + ]: 22796 : if (!found)
153 : 366 : revokeitems[nrevokeitems++] = baseitems[i];
154 : : }
155 : :
156 : : /* Prepare working buffers */
7625 157 : 22971 : grantee = createPQExpBuffer();
158 : 22971 : grantor = createPQExpBuffer();
159 : 22971 : privs = createPQExpBuffer();
160 : 22971 : privswgo = createPQExpBuffer();
161 : :
162 : : /*
163 : : * At the end, these two will be pasted together to form the result.
164 : : */
7570 peter_e@gmx.net 165 : 22971 : firstsql = createPQExpBuffer();
166 : 22971 : secondsql = createPQExpBuffer();
167 : :
168 : : /*
169 : : * Build REVOKE statements for ACLs listed in revokeitems[].
170 : : */
847 tgl@sss.pgh.pa.us 171 [ + + ]: 23337 : for (i = 0; i < nrevokeitems; i++)
172 : : {
173 [ - + ]: 366 : if (!parseAclItem(revokeitems[i],
174 : : type, name, subname, remoteVersion,
175 : : grantee, grantor, privs, NULL))
176 : : {
847 tgl@sss.pgh.pa.us 177 :UBC 0 : ok = false;
178 : 0 : break;
179 : : }
180 : :
847 tgl@sss.pgh.pa.us 181 [ + - ]:CBC 366 : if (privs->len > 0)
182 : : {
183 : 366 : appendPQExpBuffer(firstsql, "%sREVOKE %s ON %s ",
184 : : prefix, privs->data, type);
185 [ + + + - ]: 366 : if (nspname && *nspname)
186 : 212 : appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
489 jdavis@postgresql.or 187 [ + - + + ]: 366 : if (name && *name)
188 : 298 : appendPQExpBuffer(firstsql, "%s ", name);
189 : 366 : appendPQExpBufferStr(firstsql, "FROM ");
847 tgl@sss.pgh.pa.us 190 [ + + ]: 366 : if (grantee->len == 0)
191 : 190 : appendPQExpBufferStr(firstsql, "PUBLIC;\n");
192 : : else
193 : 176 : appendPQExpBuffer(firstsql, "%s;\n",
194 : 176 : fmtId(grantee->data));
195 : : }
196 : : }
197 : :
198 : : /*
199 : : * At this point we have issued REVOKE statements for all initial and
200 : : * default privileges that are no longer present on the object, so we are
201 : : * almost ready to GRANT the privileges listed in grantitems[].
202 : : *
203 : : * We still need some hacking though to cover the case where new default
204 : : * public privileges are added in new versions: the REVOKE ALL will revoke
205 : : * them, leading to behavior different from what the old version had,
206 : : * which is generally not what's wanted. So add back default privs if the
207 : : * source database is too old to have had that particular priv. (As of
208 : : * right now, no such cases exist in supported versions.)
209 : : */
210 : :
211 : : /*
212 : : * Scan individual ACL items to be granted.
213 : : *
214 : : * The order in which privileges appear in the ACL string (the order they
215 : : * have been GRANT'd in, which the backend maintains) must be preserved to
216 : : * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on
217 : : * those are dumped in the correct order. However, some old server
218 : : * versions will show grants to PUBLIC before the owner's own grants; for
219 : : * consistency's sake, force the owner's grants to be output first.
220 : : */
860 221 [ + + ]: 24859 : for (i = 0; i < ngrantitems; i++)
222 : : {
223 [ + - ]: 1888 : if (parseAclItem(grantitems[i], type, name, subname, remoteVersion,
224 : : grantee, grantor, privs, privswgo))
225 : : {
226 : : /*
227 : : * If the grantor isn't the owner, we'll need to use SET SESSION
228 : : * AUTHORIZATION to become the grantor. Issue the SET/RESET only
229 : : * if there's something useful to do.
230 : : */
231 [ + + + - ]: 1888 : if (privs->len > 0 || privswgo->len > 0)
232 : : {
233 : : PQExpBuffer thissql;
234 : :
235 : : /* Set owner as grantor if that's not explicit in the ACL */
236 [ - + - - ]: 1888 : if (grantor->len == 0 && owner)
860 tgl@sss.pgh.pa.us 237 :UBC 0 : printfPQExpBuffer(grantor, "%s", owner);
238 : :
239 : : /* Make sure owner's own grants are output before others */
860 tgl@sss.pgh.pa.us 240 [ + - ]:CBC 1888 : if (owner &&
241 [ + + ]: 1888 : strcmp(grantee->data, owner) == 0 &&
242 [ + - ]: 129 : strcmp(grantor->data, owner) == 0)
243 : 129 : thissql = firstsql;
244 : : else
245 : 1759 : thissql = secondsql;
246 : :
7570 peter_e@gmx.net 247 [ + - ]: 1888 : if (grantor->len > 0
248 [ + - - + ]: 1888 : && (!owner || strcmp(owner, grantor->data) != 0))
860 tgl@sss.pgh.pa.us 249 :UBC 0 : appendPQExpBuffer(thissql, "SET SESSION AUTHORIZATION %s;\n",
7570 peter_e@gmx.net 250 : 0 : fmtId(grantor->data));
251 : :
7625 tgl@sss.pgh.pa.us 252 [ + + ]:CBC 1888 : if (privs->len > 0)
253 : : {
860 254 : 1886 : appendPQExpBuffer(thissql, "%sGRANT %s ON %s ",
255 : : prefix, privs->data, type);
2239 256 [ + + + - ]: 1886 : if (nspname && *nspname)
860 257 : 1627 : appendPQExpBuffer(thissql, "%s.", fmtId(nspname));
489 jdavis@postgresql.or 258 [ + - + + ]: 1886 : if (name && *name)
259 : 1782 : appendPQExpBuffer(thissql, "%s ", name);
260 : 1886 : appendPQExpBufferStr(thissql, "TO ");
7625 tgl@sss.pgh.pa.us 261 [ + + ]: 1886 : if (grantee->len == 0)
860 262 : 1094 : appendPQExpBufferStr(thissql, "PUBLIC;\n");
263 : : else
264 : 792 : appendPQExpBuffer(thissql, "%s;\n", fmtId(grantee->data));
265 : : }
7625 266 [ + + ]: 1888 : if (privswgo->len > 0)
267 : : {
860 268 : 20 : appendPQExpBuffer(thissql, "%sGRANT %s ON %s ",
269 : : prefix, privswgo->data, type);
2239 270 [ + + + - ]: 20 : if (nspname && *nspname)
860 271 : 19 : appendPQExpBuffer(thissql, "%s.", fmtId(nspname));
489 jdavis@postgresql.or 272 [ + - + - ]: 20 : if (name && *name)
273 : 20 : appendPQExpBuffer(thissql, "%s ", name);
274 : 20 : appendPQExpBufferStr(thissql, "TO ");
7625 tgl@sss.pgh.pa.us 275 [ - + ]: 20 : if (grantee->len == 0)
860 tgl@sss.pgh.pa.us 276 :UBC 0 : appendPQExpBufferStr(thissql, "PUBLIC");
277 : : else
860 tgl@sss.pgh.pa.us 278 :CBC 20 : appendPQExpBufferStr(thissql, fmtId(grantee->data));
279 : 20 : appendPQExpBufferStr(thissql, " WITH GRANT OPTION;\n");
280 : : }
281 : :
7570 peter_e@gmx.net 282 [ + - ]: 1888 : if (grantor->len > 0
283 [ + - - + ]: 1888 : && (!owner || strcmp(owner, grantor->data) != 0))
860 tgl@sss.pgh.pa.us 284 :UBC 0 : appendPQExpBufferStr(thissql, "RESET SESSION AUTHORIZATION;\n");
285 : : }
286 : : }
287 : : else
288 : : {
289 : : /* parseAclItem failed, give up */
290 : 0 : ok = false;
291 : 0 : break;
292 : : }
293 : : }
294 : :
7625 tgl@sss.pgh.pa.us 295 :CBC 22971 : destroyPQExpBuffer(grantee);
296 : 22971 : destroyPQExpBuffer(grantor);
297 : 22971 : destroyPQExpBuffer(privs);
298 : 22971 : destroyPQExpBuffer(privswgo);
299 : :
7570 peter_e@gmx.net 300 : 22971 : appendPQExpBuffer(sql, "%s%s", firstsql->data, secondsql->data);
301 : 22971 : destroyPQExpBuffer(firstsql);
302 : 22971 : destroyPQExpBuffer(secondsql);
303 : :
668 peter@eisentraut.org 304 : 22971 : free(aclitems);
305 : 22971 : free(baseitems);
306 : 22971 : free(grantitems);
307 : 22971 : free(revokeitems);
308 : :
3350 tgl@sss.pgh.pa.us 309 : 22971 : return ok;
310 : : }
311 : :
312 : : /*
313 : : * Build ALTER DEFAULT PRIVILEGES command(s) for a single pg_default_acl entry.
314 : : *
315 : : * type: the object type (TABLES, FUNCTIONS, etc)
316 : : * nspname: schema name, or NULL for global default privileges
317 : : * acls: the ACL string fetched from the database
318 : : * acldefault: the appropriate default ACL for the object type and owner
319 : : * owner: username of privileges owner (will be passed through fmtId)
320 : : * remoteVersion: version of database
321 : : *
322 : : * Returns true if okay, false if could not parse the acl string.
323 : : * The resulting commands (if any) are appended to the contents of 'sql'.
324 : : */
325 : : bool
5305 326 : 138 : buildDefaultACLCommands(const char *type, const char *nspname,
327 : : const char *acls, const char *acldefault,
328 : : const char *owner,
329 : : int remoteVersion,
330 : : PQExpBuffer sql)
331 : : {
332 : : PQExpBuffer prefix;
333 : :
334 : 138 : prefix = createPQExpBuffer();
335 : :
336 : : /*
337 : : * We incorporate the target role directly into the command, rather than
338 : : * playing around with SET ROLE or anything like that. This is so that a
339 : : * permissions error leads to nothing happening, rather than changing
340 : : * default privileges for the wrong user.
341 : : */
342 : 138 : appendPQExpBuffer(prefix, "ALTER DEFAULT PRIVILEGES FOR ROLE %s ",
343 : : fmtId(owner));
344 [ + + ]: 138 : if (nspname)
345 : 70 : appendPQExpBuffer(prefix, "IN SCHEMA %s ", fmtId(nspname));
346 : :
347 : : /*
348 : : * There's no such thing as initprivs for a default ACL, so the base ACL
349 : : * is always just the object-type-specific default.
350 : : */
2239 351 [ - + ]: 138 : if (!buildACLCommands("", NULL, NULL, type,
352 : : acls, acldefault, owner,
2630 sfrost@snowman.net 353 : 138 : prefix->data, remoteVersion, sql))
354 : : {
2535 sfrost@snowman.net 355 :UBC 0 : destroyPQExpBuffer(prefix);
2630 356 : 0 : return false;
357 : : }
358 : :
5305 tgl@sss.pgh.pa.us 359 :CBC 138 : destroyPQExpBuffer(prefix);
360 : :
2630 sfrost@snowman.net 361 : 138 : return true;
362 : : }
363 : :
364 : : /*
365 : : * This will parse an aclitem string, having the general form
366 : : * username=privilegecodes/grantor
367 : : *
368 : : * Returns true on success, false on parse error. On success, the components
369 : : * of the string are returned in the PQExpBuffer parameters.
370 : : *
371 : : * The returned grantee string will be the dequoted username, or an empty
372 : : * string in the case of a grant to PUBLIC. The returned grantor is the
373 : : * dequoted grantor name. Privilege characters are translated to GRANT/REVOKE
374 : : * comma-separated privileges lists. If "privswgo" is non-NULL, the result is
375 : : * separate lists for privileges with grant option ("privswgo") and without
376 : : * ("privs"). Otherwise, "privs" bears every relevant privilege, ignoring the
377 : : * grant option distinction.
378 : : *
379 : : * Note: for cross-version compatibility, it's important to use ALL to
380 : : * represent the privilege sets whenever appropriate.
381 : : */
382 : : static bool
5561 tgl@sss.pgh.pa.us 383 : 2254 : parseAclItem(const char *item, const char *type,
384 : : const char *name, const char *subname, int remoteVersion,
385 : : PQExpBuffer grantee, PQExpBuffer grantor,
386 : : PQExpBuffer privs, PQExpBuffer privswgo)
387 : : {
388 : : char *buf;
7625 389 : 2254 : bool all_with_go = true;
390 : 2254 : bool all_without_go = true;
391 : : char *eqpos;
392 : : char *slpos;
393 : : char *pos;
394 : :
1807 michael@paquier.xyz 395 : 2254 : buf = pg_strdup(item);
396 : :
397 : : /* user or group name is string up to = */
860 tgl@sss.pgh.pa.us 398 : 2254 : eqpos = dequoteAclUserName(grantee, buf);
7563 399 [ - + ]: 2254 : if (*eqpos != '=')
400 : : {
1807 michael@paquier.xyz 401 :UBC 0 : pg_free(buf);
7625 tgl@sss.pgh.pa.us 402 : 0 : return false;
403 : : }
404 : :
405 : : /* grantor should appear after / */
7625 tgl@sss.pgh.pa.us 406 :CBC 2254 : slpos = strchr(eqpos + 1, '/');
407 [ + - ]: 2254 : if (slpos)
408 : : {
7563 409 : 2254 : *slpos++ = '\0';
860 410 : 2254 : slpos = dequoteAclUserName(grantor, slpos);
7563 411 [ - + ]: 2254 : if (*slpos != '\0')
412 : : {
1807 michael@paquier.xyz 413 :UBC 0 : pg_free(buf);
7563 tgl@sss.pgh.pa.us 414 : 0 : return false;
415 : : }
416 : : }
417 : : else
418 : : {
1807 michael@paquier.xyz 419 : 0 : pg_free(buf);
2741 tgl@sss.pgh.pa.us 420 : 0 : return false;
421 : : }
422 : :
423 : : /* privilege codes */
424 : : #define CONVERT_PRIV(code, keywd) \
425 : : do { \
426 : : if ((pos = strchr(eqpos + 1, code))) \
427 : : { \
428 : : if (*(pos + 1) == '*' && privswgo != NULL) \
429 : : { \
430 : : AddAcl(privswgo, keywd, subname); \
431 : : all_without_go = false; \
432 : : } \
433 : : else \
434 : : { \
435 : : AddAcl(privs, keywd, subname); \
436 : : all_with_go = false; \
437 : : } \
438 : : } \
439 : : else \
440 : : all_with_go = all_without_go = false; \
441 : : } while (0)
442 : :
7625 tgl@sss.pgh.pa.us 443 :CBC 2254 : resetPQExpBuffer(privs);
444 : 2254 : resetPQExpBuffer(privswgo);
445 : :
5298 446 [ + + + + ]: 2254 : if (strcmp(type, "TABLE") == 0 || strcmp(type, "SEQUENCE") == 0 ||
447 [ + + - + ]: 714 : strcmp(type, "TABLES") == 0 || strcmp(type, "SEQUENCES") == 0)
448 : : {
7625 449 [ + + - + : 1643 : CONVERT_PRIV('r', "SELECT");
- - ]
450 : :
5298 451 [ + + ]: 1643 : if (strcmp(type, "SEQUENCE") == 0 ||
452 [ - + ]: 1588 : strcmp(type, "SEQUENCES") == 0)
453 : : /* sequence only */
6658 bruce@momjian.us 454 [ + + + + : 109 : CONVERT_PRIV('U', "USAGE");
+ + ]
455 : : else
456 : : {
457 : : /* table only */
458 [ + + - + : 1588 : CONVERT_PRIV('a', "INSERT");
- - ]
2741 tgl@sss.pgh.pa.us 459 [ + + - + : 1588 : CONVERT_PRIV('x', "REFERENCES");
- - ]
460 : : /* rest are not applicable to columns */
5561 461 [ + + ]: 1588 : if (subname == NULL)
462 : : {
2741 463 [ + + - + : 418 : CONVERT_PRIV('d', "DELETE");
- - ]
464 [ + + - + : 418 : CONVERT_PRIV('t', "TRIGGER");
- - ]
852 465 [ + + - + : 418 : CONVERT_PRIV('D', "TRUNCATE");
- - ]
32 nathan@postgresql.or 466 [ + + - + :GNC 418 : CONVERT_PRIV('m', "MAINTAIN");
- - ]
467 : : }
468 : : }
469 : :
470 : : /* UPDATE */
2741 tgl@sss.pgh.pa.us 471 [ + + + + :CBC 1756 : CONVERT_PRIV('w', "UPDATE");
+ + ]
472 : : }
5298 473 [ + + ]: 611 : else if (strcmp(type, "FUNCTION") == 0 ||
474 [ + + ]: 452 : strcmp(type, "FUNCTIONS") == 0)
7625 475 [ + - + + : 456 : CONVERT_PRIV('X', "EXECUTE");
+ + ]
2327 peter_e@gmx.net 476 [ + - ]: 383 : else if (strcmp(type, "PROCEDURE") == 0 ||
477 [ - + ]: 383 : strcmp(type, "PROCEDURES") == 0)
2327 peter_e@gmx.net 478 [ # # # # :UBC 0 : CONVERT_PRIV('X', "EXECUTE");
# # ]
7625 tgl@sss.pgh.pa.us 479 [ + + ]:CBC 383 : else if (strcmp(type, "LANGUAGE") == 0)
480 [ + - - + : 41 : CONVERT_PRIV('U', "USAGE");
- - ]
2574 teodor@sigaev.ru 481 [ + + ]: 342 : else if (strcmp(type, "SCHEMA") == 0 ||
2525 tgl@sss.pgh.pa.us 482 [ - + ]: 262 : strcmp(type, "SCHEMAS") == 0)
483 : : {
7625 484 [ + + - + : 80 : CONVERT_PRIV('C', "CREATE");
- - ]
485 [ + - - + : 160 : CONVERT_PRIV('U', "USAGE");
- - ]
486 : : }
487 [ + + ]: 262 : else if (strcmp(type, "DATABASE") == 0)
488 : : {
489 [ + + - + : 14 : CONVERT_PRIV('C', "CREATE");
- - ]
6559 490 [ + + - + : 14 : CONVERT_PRIV('c', "CONNECT");
- - ]
7625 491 [ + + - + : 14 : CONVERT_PRIV('T', "TEMPORARY");
- - ]
492 : : }
7240 493 [ - + ]: 248 : else if (strcmp(type, "TABLESPACE") == 0)
7240 tgl@sss.pgh.pa.us 494 [ # # # # :UBC 0 : CONVERT_PRIV('C', "CREATE");
# # ]
4144 tgl@sss.pgh.pa.us 495 [ + + ]:CBC 248 : else if (strcmp(type, "TYPE") == 0 ||
496 [ - + ]: 107 : strcmp(type, "TYPES") == 0)
497 [ + - - + : 282 : CONVERT_PRIV('U', "USAGE");
- - ]
5595 peter_e@gmx.net 498 [ + + ]: 107 : else if (strcmp(type, "FOREIGN DATA WRAPPER") == 0)
499 [ + - - + : 34 : CONVERT_PRIV('U', "USAGE");
- - ]
5156 heikki.linnakangas@i 500 [ + + ]: 73 : else if (strcmp(type, "FOREIGN SERVER") == 0)
5595 peter_e@gmx.net 501 [ + - - + : 34 : CONVERT_PRIV('U', "USAGE");
- - ]
4852 rhaas@postgresql.org 502 [ - + ]: 39 : else if (strcmp(type, "FOREIGN TABLE") == 0)
4852 rhaas@postgresql.org 503 [ # # # # :UBC 0 : CONVERT_PRIV('r', "SELECT");
# # ]
739 tgl@sss.pgh.pa.us 504 [ + + ]:CBC 39 : else if (strcmp(type, "PARAMETER") == 0)
505 : : {
506 [ + + + + : 3 : CONVERT_PRIV('s', "SET");
+ - ]
507 [ + - + + : 3 : CONVERT_PRIV('A', "ALTER SYSTEM");
+ - ]
508 : : }
5238 itagaki.takahiro@gma 509 [ + - ]: 36 : else if (strcmp(type, "LARGE OBJECT") == 0)
510 : : {
511 [ + - - + : 36 : CONVERT_PRIV('r', "SELECT");
- - ]
512 [ + - - + : 36 : CONVERT_PRIV('w', "UPDATE");
- - ]
513 : : }
514 : : else
7625 tgl@sss.pgh.pa.us 515 :UBC 0 : abort();
516 : :
517 : : #undef CONVERT_PRIV
518 : :
7625 tgl@sss.pgh.pa.us 519 [ + + ]:CBC 2254 : if (all_with_go)
520 : : {
521 : 2 : resetPQExpBuffer(privs);
522 : 2 : printfPQExpBuffer(privswgo, "ALL");
5561 523 [ - + ]: 2 : if (subname)
5561 tgl@sss.pgh.pa.us 524 :UBC 0 : appendPQExpBuffer(privswgo, "(%s)", subname);
525 : : }
7625 tgl@sss.pgh.pa.us 526 [ + + ]:CBC 2252 : else if (all_without_go)
527 : : {
528 : 609 : resetPQExpBuffer(privswgo);
529 : 609 : printfPQExpBuffer(privs, "ALL");
5561 530 [ - + ]: 609 : if (subname)
5561 tgl@sss.pgh.pa.us 531 :UBC 0 : appendPQExpBuffer(privs, "(%s)", subname);
532 : : }
533 : :
1807 michael@paquier.xyz 534 :CBC 2254 : pg_free(buf);
535 : :
7625 tgl@sss.pgh.pa.us 536 : 2254 : return true;
537 : : }
538 : :
539 : : /*
540 : : * Transfer the role name at *input into the output buffer, adding
541 : : * quoting according to the same rules as putid() in backend's acl.c.
542 : : */
543 : : void
860 544 : 456 : quoteAclUserName(PQExpBuffer output, const char *input)
545 : : {
546 : : const char *src;
547 : 456 : bool safe = true;
548 : :
549 [ + + ]: 7749 : for (src = input; *src; src++)
550 : : {
551 : : /* This test had better match what putid() does */
552 [ + + + + ]: 7446 : if (!isalnum((unsigned char) *src) && *src != '_')
553 : : {
554 : 153 : safe = false;
555 : 153 : break;
556 : : }
557 : : }
558 [ + + ]: 456 : if (!safe)
559 : 153 : appendPQExpBufferChar(output, '"');
560 [ + + ]: 9126 : for (src = input; *src; src++)
561 : : {
562 : : /* A double quote character in a username is encoded as "" */
563 [ + + ]: 8670 : if (*src == '"')
564 : 153 : appendPQExpBufferChar(output, '"');
565 : 8670 : appendPQExpBufferChar(output, *src);
566 : : }
567 [ + + ]: 456 : if (!safe)
568 : 153 : appendPQExpBufferChar(output, '"');
569 : 456 : }
570 : :
571 : : /*
572 : : * Transfer a user or group name starting at *input into the output buffer,
573 : : * dequoting if needed. Returns a pointer to just past the input name.
574 : : * The name is taken to end at an unquoted '=' or end of string.
575 : : * Note: unlike quoteAclUserName(), this first clears the output buffer.
576 : : */
577 : : static char *
578 : 4508 : dequoteAclUserName(PQExpBuffer output, char *input)
579 : : {
7563 580 : 4508 : resetPQExpBuffer(output);
581 : :
582 [ + + + + ]: 39258 : while (*input && *input != '=')
583 : : {
584 : : /*
585 : : * If user name isn't quoted, then just add it to the output buffer
586 : : */
587 [ + + ]: 34750 : if (*input != '"')
588 : 34647 : appendPQExpBufferChar(output, *input++);
589 : : else
590 : : {
591 : : /* Otherwise, it's a quoted username */
592 : 103 : input++;
593 : : /* Loop until we come across an unescaped quote */
7549 594 [ + + + + ]: 2472 : while (!(*input == '"' && *(input + 1) != '"'))
595 : : {
7563 596 [ - + ]: 2369 : if (*input == '\0')
2489 tgl@sss.pgh.pa.us 597 :UBC 0 : return input; /* really a syntax error... */
598 : :
599 : : /*
600 : : * Quoting convention is to escape " as "". Keep this code in
601 : : * sync with putid() in backend's acl.c.
602 : : */
7549 tgl@sss.pgh.pa.us 603 [ + + + - ]:CBC 2369 : if (*input == '"' && *(input + 1) == '"')
604 : 103 : input++;
7563 605 : 2369 : appendPQExpBufferChar(output, *input++);
606 : : }
607 : 103 : input++;
608 : : }
609 : : }
610 : 4508 : return input;
611 : : }
612 : :
613 : : /*
614 : : * Append a privilege keyword to a keyword list, inserting comma if needed.
615 : : */
616 : : static void
5561 617 : 2914 : AddAcl(PQExpBuffer aclbuf, const char *keyword, const char *subname)
618 : : {
7625 619 [ + + ]: 2914 : if (aclbuf->len > 0)
620 : 642 : appendPQExpBufferChar(aclbuf, ',');
3800 heikki.linnakangas@i 621 : 2914 : appendPQExpBufferStr(aclbuf, keyword);
5561 tgl@sss.pgh.pa.us 622 [ + + ]: 2914 : if (subname)
623 : 1170 : appendPQExpBuffer(aclbuf, "(%s)", subname);
7625 624 : 2914 : }
625 : :
626 : :
627 : : /*
628 : : * buildShSecLabelQuery
629 : : *
630 : : * Build a query to retrieve security labels for a shared object.
631 : : * The object is identified by its OID plus the name of the catalog
632 : : * it can be found in (e.g., "pg_database" for database names).
633 : : * The query is appended to "sql". (We don't execute it here so as to
634 : : * keep this file free of assumptions about how to deal with SQL errors.)
635 : : */
636 : : void
1328 peter@eisentraut.org 637 : 131 : buildShSecLabelQuery(const char *catalog_name, Oid objectId,
638 : : PQExpBuffer sql)
639 : : {
4652 rhaas@postgresql.org 640 : 131 : appendPQExpBuffer(sql,
641 : : "SELECT provider, label FROM pg_catalog.pg_shseclabel "
642 : : "WHERE classoid = 'pg_catalog.%s'::pg_catalog.regclass "
643 : : "AND objoid = '%u'", catalog_name, objectId);
644 : 131 : }
645 : :
646 : : /*
647 : : * emitShSecLabels
648 : : *
649 : : * Construct SECURITY LABEL commands using the data retrieved by the query
650 : : * generated by buildShSecLabelQuery, and append them to "buffer".
651 : : * Here, the target object is identified by its type name (e.g. "DATABASE")
652 : : * and its name (not pre-quoted).
653 : : */
654 : : void
655 : 131 : emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer,
656 : : const char *objtype, const char *objname)
657 : : {
658 : : int i;
659 : :
660 [ - + ]: 131 : for (i = 0; i < PQntuples(res); i++)
661 : : {
4326 bruce@momjian.us 662 :UBC 0 : char *provider = PQgetvalue(res, i, 0);
663 : 0 : char *label = PQgetvalue(res, i, 1);
664 : :
665 : : /* must use fmtId result before calling it again */
4652 rhaas@postgresql.org 666 : 0 : appendPQExpBuffer(buffer,
667 : : "SECURITY LABEL FOR %s ON %s",
668 : : fmtId(provider), objtype);
669 : 0 : appendPQExpBuffer(buffer,
670 : : " %s IS ",
671 : : fmtId(objname));
672 : 0 : appendStringLiteralConn(buffer, label, conn);
3800 heikki.linnakangas@i 673 : 0 : appendPQExpBufferStr(buffer, ";\n");
674 : : }
4652 rhaas@postgresql.org 675 :CBC 131 : }
676 : :
677 : :
678 : : /*
679 : : * Detect whether the given GUC variable is of GUC_LIST_QUOTE type.
680 : : *
681 : : * It'd be better if we could inquire this directly from the backend; but even
682 : : * if there were a function for that, it could only tell us about variables
683 : : * currently known to guc.c, so that it'd be unsafe for extensions to declare
684 : : * GUC_LIST_QUOTE variables anyway. Lacking a solution for that, it doesn't
685 : : * seem worth the work to do more than have this list, which must be kept in
686 : : * sync with the variables actually marked GUC_LIST_QUOTE in guc_tables.c.
687 : : */
688 : : bool
2216 tgl@sss.pgh.pa.us 689 : 65 : variable_is_guc_list_quote(const char *name)
690 : : {
1254 michael@paquier.xyz 691 [ + + + + ]: 125 : if (pg_strcasecmp(name, "local_preload_libraries") == 0 ||
692 [ + - ]: 115 : pg_strcasecmp(name, "search_path") == 0 ||
2216 tgl@sss.pgh.pa.us 693 [ + - ]: 110 : pg_strcasecmp(name, "session_preload_libraries") == 0 ||
694 [ + - ]: 110 : pg_strcasecmp(name, "shared_preload_libraries") == 0 ||
1254 michael@paquier.xyz 695 [ - + ]: 110 : pg_strcasecmp(name, "temp_tablespaces") == 0 ||
696 : 55 : pg_strcasecmp(name, "unix_socket_directories") == 0)
2216 tgl@sss.pgh.pa.us 697 : 10 : return true;
698 : : else
699 : 55 : return false;
700 : : }
701 : :
702 : : /*
703 : : * SplitGUCList --- parse a string containing identifiers or file names
704 : : *
705 : : * This is used to split the value of a GUC_LIST_QUOTE GUC variable, without
706 : : * presuming whether the elements will be taken as identifiers or file names.
707 : : * See comparable code in src/backend/utils/adt/varlena.c.
708 : : *
709 : : * Inputs:
710 : : * rawstring: the input string; must be overwritable! On return, it's
711 : : * been modified to contain the separated identifiers.
712 : : * separator: the separator punctuation expected between identifiers
713 : : * (typically '.' or ','). Whitespace may also appear around
714 : : * identifiers.
715 : : * Outputs:
716 : : * namelist: receives a malloc'd, null-terminated array of pointers to
717 : : * identifiers within rawstring. Caller should free this
718 : : * even on error return.
719 : : *
720 : : * Returns true if okay, false if there is a syntax error in the string.
721 : : */
722 : : bool
2084 723 : 10 : SplitGUCList(char *rawstring, char separator,
724 : : char ***namelist)
725 : : {
726 : 10 : char *nextp = rawstring;
727 : 10 : bool done = false;
728 : : char **nextptr;
729 : :
730 : : /*
731 : : * Since we disallow empty identifiers, this is a conservative
732 : : * overestimate of the number of pointers we could need. Allow one for
733 : : * list terminator.
734 : : */
735 : 10 : *namelist = nextptr = (char **)
736 : 10 : pg_malloc((strlen(rawstring) / 2 + 2) * sizeof(char *));
737 : 10 : *nextptr = NULL;
738 : :
739 [ - + ]: 10 : while (isspace((unsigned char) *nextp))
2084 tgl@sss.pgh.pa.us 740 :UBC 0 : nextp++; /* skip leading whitespace */
741 : :
2084 tgl@sss.pgh.pa.us 742 [ - + ]:CBC 10 : if (*nextp == '\0')
2084 tgl@sss.pgh.pa.us 743 :UBC 0 : return true; /* allow empty string */
744 : :
745 : : /* At the top of the loop, we are at start of a new identifier. */
746 : : do
747 : : {
748 : : char *curname;
749 : : char *endp;
750 : :
2084 tgl@sss.pgh.pa.us 751 [ + + ]:CBC 25 : if (*nextp == '"')
752 : : {
753 : : /* Quoted name --- collapse quote-quote pairs */
754 : 20 : curname = nextp + 1;
755 : : for (;;)
756 : : {
757 : 30 : endp = strchr(nextp + 1, '"');
758 [ - + ]: 25 : if (endp == NULL)
2084 tgl@sss.pgh.pa.us 759 :UBC 0 : return false; /* mismatched quotes */
2084 tgl@sss.pgh.pa.us 760 [ + + ]:CBC 25 : if (endp[1] != '"')
761 : 20 : break; /* found end of quoted name */
762 : : /* Collapse adjacent quotes into one quote, and look again */
763 : 5 : memmove(endp, endp + 1, strlen(endp));
764 : 5 : nextp = endp;
765 : : }
766 : : /* endp now points at the terminating quote */
767 : 20 : nextp = endp + 1;
768 : : }
769 : : else
770 : : {
771 : : /* Unquoted name --- extends to separator or whitespace */
772 : 5 : curname = nextp;
773 [ + + + - ]: 55 : while (*nextp && *nextp != separator &&
774 [ + - ]: 50 : !isspace((unsigned char) *nextp))
775 : 50 : nextp++;
776 : 5 : endp = nextp;
777 [ - + ]: 5 : if (curname == nextp)
2084 tgl@sss.pgh.pa.us 778 :UBC 0 : return false; /* empty unquoted name not allowed */
779 : : }
780 : :
2084 tgl@sss.pgh.pa.us 781 [ - + ]:CBC 25 : while (isspace((unsigned char) *nextp))
2084 tgl@sss.pgh.pa.us 782 :UBC 0 : nextp++; /* skip trailing whitespace */
783 : :
2084 tgl@sss.pgh.pa.us 784 [ + + ]:CBC 25 : if (*nextp == separator)
785 : : {
786 : 15 : nextp++;
787 [ + + ]: 30 : while (isspace((unsigned char) *nextp))
788 : 15 : nextp++; /* skip leading whitespace for next */
789 : : /* we expect another name, so done remains false */
790 : : }
791 [ + - ]: 10 : else if (*nextp == '\0')
792 : 10 : done = true;
793 : : else
2084 tgl@sss.pgh.pa.us 794 :UBC 0 : return false; /* invalid syntax */
795 : :
796 : : /* Now safe to overwrite separator with a null */
2084 tgl@sss.pgh.pa.us 797 :CBC 25 : *endp = '\0';
798 : :
799 : : /*
800 : : * Finished isolating current name --- add it to output array
801 : : */
802 : 25 : *nextptr++ = curname;
803 : :
804 : : /* Loop back if we didn't reach end of string */
805 [ + + ]: 25 : } while (!done);
806 : :
807 : 10 : *nextptr = NULL;
808 : 10 : return true;
809 : : }
810 : :
811 : : /*
812 : : * Helper function for dumping "ALTER DATABASE/ROLE SET ..." commands.
813 : : *
814 : : * Parse the contents of configitem (a "name=value" string), wrap it in
815 : : * a complete ALTER command, and append it to buf.
816 : : *
817 : : * type is DATABASE or ROLE, and name is the name of the database or role.
818 : : * If we need an "IN" clause, type2 and name2 similarly define what to put
819 : : * there; otherwise they should be NULL.
820 : : * conn is used only to determine string-literal quoting conventions.
821 : : */
822 : : void
2274 823 : 30 : makeAlterConfigCommand(PGconn *conn, const char *configitem,
824 : : const char *type, const char *name,
825 : : const char *type2, const char *name2,
826 : : PQExpBuffer buf)
827 : : {
828 : : char *mine;
829 : : char *pos;
830 : :
831 : : /* Parse the configitem. If we can't find an "=", silently do nothing. */
832 : 30 : mine = pg_strdup(configitem);
833 : 30 : pos = strchr(mine, '=');
834 [ - + ]: 30 : if (pos == NULL)
835 : : {
2274 tgl@sss.pgh.pa.us 836 :UBC 0 : pg_free(mine);
837 : 0 : return;
838 : : }
2274 tgl@sss.pgh.pa.us 839 :CBC 30 : *pos++ = '\0';
840 : :
841 : : /* Build the command, with suitable quoting for everything. */
842 : 30 : appendPQExpBuffer(buf, "ALTER %s %s ", type, fmtId(name));
843 [ - + - - ]: 30 : if (type2 != NULL && name2 != NULL)
2274 tgl@sss.pgh.pa.us 844 :UBC 0 : appendPQExpBuffer(buf, "IN %s %s ", type2, fmtId(name2));
2274 tgl@sss.pgh.pa.us 845 :CBC 30 : appendPQExpBuffer(buf, "SET %s TO ", fmtId(mine));
846 : :
847 : : /*
848 : : * Variables that are marked GUC_LIST_QUOTE were already fully quoted by
849 : : * flatten_set_variable_args() before they were put into the setconfig
850 : : * array. However, because the quoting rules used there aren't exactly
851 : : * like SQL's, we have to break the list value apart and then quote the
852 : : * elements as string literals. (The elements may be double-quoted as-is,
853 : : * but we can't just feed them to the SQL parser; it would do the wrong
854 : : * thing with elements that are zero-length or longer than NAMEDATALEN.)
855 : : *
856 : : * Variables that are not so marked should just be emitted as simple
857 : : * string literals. If the variable is not known to
858 : : * variable_is_guc_list_quote(), we'll do that; this makes it unsafe to
859 : : * use GUC_LIST_QUOTE for extension variables.
860 : : */
2216 861 [ - + ]: 30 : if (variable_is_guc_list_quote(mine))
862 : : {
863 : : char **namelist;
864 : : char **nameptr;
865 : :
866 : : /* Parse string into list of identifiers */
867 : : /* this shouldn't fail really */
2084 tgl@sss.pgh.pa.us 868 [ # # ]:UBC 0 : if (SplitGUCList(pos, ',', &namelist))
869 : : {
870 [ # # ]: 0 : for (nameptr = namelist; *nameptr; nameptr++)
871 : : {
872 [ # # ]: 0 : if (nameptr != namelist)
873 : 0 : appendPQExpBufferStr(buf, ", ");
874 : 0 : appendStringLiteralConn(buf, *nameptr, conn);
875 : : }
876 : : }
877 : 0 : pg_free(namelist);
878 : : }
879 : : else
2274 tgl@sss.pgh.pa.us 880 :CBC 30 : appendStringLiteralConn(buf, pos, conn);
881 : :
882 : 30 : appendPQExpBufferStr(buf, ";\n");
883 : :
884 : 30 : pg_free(mine);
885 : : }
|