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