Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * adminpack.c
4 : *
5 : *
6 : * Copyright (c) 2002-2023, PostgreSQL Global Development Group
7 : *
8 : * Author: Andreas Pflug <pgadmin@pse-consulting.de>
9 : *
10 : * IDENTIFICATION
11 : * contrib/adminpack/adminpack.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include <sys/file.h>
18 : #include <sys/stat.h>
19 : #include <unistd.h>
20 :
21 : #include "catalog/pg_authid.h"
22 : #include "catalog/pg_type.h"
23 : #include "funcapi.h"
24 : #include "miscadmin.h"
25 : #include "postmaster/syslogger.h"
26 : #include "storage/fd.h"
27 : #include "utils/acl.h"
28 : #include "utils/builtins.h"
29 : #include "utils/datetime.h"
30 :
31 :
32 : #ifdef WIN32
33 :
34 : #ifdef rename
35 : #undef rename
36 : #endif
37 :
38 : #ifdef unlink
39 : #undef unlink
40 : #endif
41 : #endif
42 :
43 2 : PG_MODULE_MAGIC;
44 :
45 2 : PG_FUNCTION_INFO_V1(pg_file_write);
46 8 : PG_FUNCTION_INFO_V1(pg_file_write_v1_1);
47 4 : PG_FUNCTION_INFO_V1(pg_file_sync);
48 2 : PG_FUNCTION_INFO_V1(pg_file_rename);
49 4 : PG_FUNCTION_INFO_V1(pg_file_rename_v1_1);
50 2 : PG_FUNCTION_INFO_V1(pg_file_unlink);
51 4 : PG_FUNCTION_INFO_V1(pg_file_unlink_v1_1);
52 2 : PG_FUNCTION_INFO_V1(pg_logdir_ls);
53 2 : PG_FUNCTION_INFO_V1(pg_logdir_ls_v1_1);
54 :
55 : static int64 pg_file_write_internal(text *file, text *data, bool replace);
56 : static bool pg_file_rename_internal(text *file1, text *file2, text *file3);
57 : static Datum pg_logdir_ls_internal(FunctionCallInfo fcinfo);
58 :
59 :
60 : /*-----------------------
61 : * some helper functions
62 : */
63 :
64 : /*
65 : * Convert a "text" filename argument to C string, and check it's allowable.
66 : *
67 : * Filename may be absolute or relative to the DataDir, but we only allow
68 : * absolute paths that match DataDir.
69 : */
70 : static char *
71 46 : convert_and_check_filename(text *arg)
72 : {
73 46 : char *filename = text_to_cstring(arg);
74 :
75 46 : canonicalize_path(filename); /* filename can change length here */
76 :
77 : /*
78 : * Members of the 'pg_write_server_files' role are allowed to access any
79 : * files on the server as the PG user, so no need to do any further checks
80 : * here.
81 : */
82 46 : if (has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
83 38 : return filename;
84 :
85 : /*
86 : * User isn't a member of the pg_write_server_files role, so check if it's
87 : * allowable
88 : */
89 8 : if (is_absolute_path(filename))
90 : {
91 : /* Allow absolute paths if within DataDir */
92 6 : if (!path_is_prefix_of_path(DataDir, filename))
93 4 : ereport(ERROR,
94 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
95 : errmsg("absolute path not allowed")));
96 : }
97 2 : else if (!path_is_relative_and_below_cwd(filename))
98 2 : ereport(ERROR,
99 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
100 : errmsg("path must be in or below the data directory")));
101 :
102 2 : return filename;
103 : }
104 :
105 :
106 : /*
107 : * check for superuser, bark if not.
108 : */
109 : static void
110 0 : requireSuperuser(void)
111 : {
112 0 : if (!superuser())
113 0 : ereport(ERROR,
114 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
115 : errmsg("only superuser may access generic file functions")));
116 0 : }
117 :
118 :
119 :
120 : /* ------------------------------------
121 : * pg_file_write - old version
122 : *
123 : * The superuser() check here must be kept as the library might be upgraded
124 : * without the extension being upgraded, meaning that in pre-1.1 installations
125 : * these functions could be called by any user.
126 : */
127 : Datum
128 0 : pg_file_write(PG_FUNCTION_ARGS)
129 : {
130 0 : text *file = PG_GETARG_TEXT_PP(0);
131 0 : text *data = PG_GETARG_TEXT_PP(1);
132 0 : bool replace = PG_GETARG_BOOL(2);
133 0 : int64 count = 0;
134 :
135 0 : requireSuperuser();
136 :
137 0 : count = pg_file_write_internal(file, data, replace);
138 :
139 0 : PG_RETURN_INT64(count);
140 : }
141 :
142 : /* ------------------------------------
143 : * pg_file_write_v1_1 - Version 1.1
144 : *
145 : * As of adminpack version 1.1, we no longer need to check if the user
146 : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
147 : * Users can then grant access to it based on their policies.
148 : *
149 : * Otherwise identical to pg_file_write (above).
150 : */
151 : Datum
152 16 : pg_file_write_v1_1(PG_FUNCTION_ARGS)
153 : {
154 16 : text *file = PG_GETARG_TEXT_PP(0);
155 16 : text *data = PG_GETARG_TEXT_PP(1);
156 16 : bool replace = PG_GETARG_BOOL(2);
157 16 : int64 count = 0;
158 :
159 16 : count = pg_file_write_internal(file, data, replace);
160 :
161 8 : PG_RETURN_INT64(count);
162 : }
163 :
164 : /* ------------------------------------
165 : * pg_file_write_internal - Workhorse for pg_file_write functions.
166 : *
167 : * This handles the actual work for pg_file_write.
168 : */
169 : static int64
170 16 : pg_file_write_internal(text *file, text *data, bool replace)
171 : {
172 : FILE *f;
173 : char *filename;
174 16 : int64 count = 0;
175 :
176 16 : filename = convert_and_check_filename(file);
177 :
178 10 : if (!replace)
179 : {
180 : struct stat fst;
181 :
182 8 : if (stat(filename, &fst) >= 0)
183 2 : ereport(ERROR,
184 : (errcode(ERRCODE_DUPLICATE_FILE),
185 : errmsg("file \"%s\" exists", filename)));
186 :
187 6 : f = AllocateFile(filename, "wb");
188 : }
189 : else
190 2 : f = AllocateFile(filename, "ab");
191 :
192 8 : if (!f)
193 0 : ereport(ERROR,
194 : (errcode_for_file_access(),
195 : errmsg("could not open file \"%s\" for writing: %m",
196 : filename)));
197 :
198 8 : count = fwrite(VARDATA_ANY(data), 1, VARSIZE_ANY_EXHDR(data), f);
199 8 : if (count != VARSIZE_ANY_EXHDR(data) || FreeFile(f))
200 0 : ereport(ERROR,
201 : (errcode_for_file_access(),
202 : errmsg("could not write file \"%s\": %m", filename)));
203 :
204 8 : return (count);
205 : }
206 :
207 : /* ------------------------------------
208 : * pg_file_sync
209 : *
210 : * We REVOKE EXECUTE on the function from PUBLIC.
211 : * Users can then grant access to it based on their policies.
212 : */
213 : Datum
214 6 : pg_file_sync(PG_FUNCTION_ARGS)
215 : {
216 : char *filename;
217 : struct stat fst;
218 :
219 6 : filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
220 :
221 6 : if (stat(filename, &fst) < 0)
222 2 : ereport(ERROR,
223 : (errcode_for_file_access(),
224 : errmsg("could not stat file \"%s\": %m", filename)));
225 :
226 4 : fsync_fname_ext(filename, S_ISDIR(fst.st_mode), false, ERROR);
227 :
228 4 : PG_RETURN_VOID();
229 : }
230 :
231 : /* ------------------------------------
232 : * pg_file_rename - old version
233 : *
234 : * The superuser() check here must be kept as the library might be upgraded
235 : * without the extension being upgraded, meaning that in pre-1.1 installations
236 : * these functions could be called by any user.
237 : */
238 : Datum
239 0 : pg_file_rename(PG_FUNCTION_ARGS)
240 : {
241 : text *file1;
242 : text *file2;
243 : text *file3;
244 : bool result;
245 :
246 0 : requireSuperuser();
247 :
248 0 : if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
249 0 : PG_RETURN_NULL();
250 :
251 0 : file1 = PG_GETARG_TEXT_PP(0);
252 0 : file2 = PG_GETARG_TEXT_PP(1);
253 :
254 0 : if (PG_ARGISNULL(2))
255 0 : file3 = NULL;
256 : else
257 0 : file3 = PG_GETARG_TEXT_PP(2);
258 :
259 0 : result = pg_file_rename_internal(file1, file2, file3);
260 :
261 0 : PG_RETURN_BOOL(result);
262 : }
263 :
264 : /* ------------------------------------
265 : * pg_file_rename_v1_1 - Version 1.1
266 : *
267 : * As of adminpack version 1.1, we no longer need to check if the user
268 : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
269 : * Users can then grant access to it based on their policies.
270 : *
271 : * Otherwise identical to pg_file_write (above).
272 : */
273 : Datum
274 6 : pg_file_rename_v1_1(PG_FUNCTION_ARGS)
275 : {
276 : text *file1;
277 : text *file2;
278 : text *file3;
279 : bool result;
280 :
281 6 : if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
282 0 : PG_RETURN_NULL();
283 :
284 6 : file1 = PG_GETARG_TEXT_PP(0);
285 6 : file2 = PG_GETARG_TEXT_PP(1);
286 :
287 6 : if (PG_ARGISNULL(2))
288 4 : file3 = NULL;
289 : else
290 2 : file3 = PG_GETARG_TEXT_PP(2);
291 :
292 6 : result = pg_file_rename_internal(file1, file2, file3);
293 :
294 6 : PG_RETURN_BOOL(result);
295 : }
296 :
297 : /* ------------------------------------
298 : * pg_file_rename_internal - Workhorse for pg_file_rename functions.
299 : *
300 : * This handles the actual work for pg_file_rename.
301 : */
302 : static bool
303 6 : pg_file_rename_internal(text *file1, text *file2, text *file3)
304 : {
305 : char *fn1,
306 : *fn2,
307 : *fn3;
308 : int rc;
309 :
310 6 : fn1 = convert_and_check_filename(file1);
311 6 : fn2 = convert_and_check_filename(file2);
312 :
313 6 : if (file3 == NULL)
314 4 : fn3 = NULL;
315 : else
316 2 : fn3 = convert_and_check_filename(file3);
317 :
318 6 : if (access(fn1, W_OK) < 0)
319 : {
320 2 : ereport(WARNING,
321 : (errcode_for_file_access(),
322 : errmsg("file \"%s\" is not accessible: %m", fn1)));
323 :
324 2 : return false;
325 : }
326 :
327 4 : if (fn3 && access(fn2, W_OK) < 0)
328 : {
329 0 : ereport(WARNING,
330 : (errcode_for_file_access(),
331 : errmsg("file \"%s\" is not accessible: %m", fn2)));
332 :
333 0 : return false;
334 : }
335 :
336 4 : rc = access(fn3 ? fn3 : fn2, W_OK);
337 4 : if (rc >= 0 || errno != ENOENT)
338 : {
339 0 : ereport(ERROR,
340 : (errcode(ERRCODE_DUPLICATE_FILE),
341 : errmsg("cannot rename to target file \"%s\"",
342 : fn3 ? fn3 : fn2)));
343 : }
344 :
345 4 : if (fn3)
346 : {
347 2 : if (rename(fn2, fn3) != 0)
348 : {
349 0 : ereport(ERROR,
350 : (errcode_for_file_access(),
351 : errmsg("could not rename \"%s\" to \"%s\": %m",
352 : fn2, fn3)));
353 : }
354 2 : if (rename(fn1, fn2) != 0)
355 : {
356 0 : ereport(WARNING,
357 : (errcode_for_file_access(),
358 : errmsg("could not rename \"%s\" to \"%s\": %m",
359 : fn1, fn2)));
360 :
361 0 : if (rename(fn3, fn2) != 0)
362 : {
363 0 : ereport(ERROR,
364 : (errcode_for_file_access(),
365 : errmsg("could not rename \"%s\" back to \"%s\": %m",
366 : fn3, fn2)));
367 : }
368 : else
369 : {
370 0 : ereport(ERROR,
371 : (errcode(ERRCODE_UNDEFINED_FILE),
372 : errmsg("renaming \"%s\" to \"%s\" was reverted",
373 : fn2, fn3)));
374 : }
375 : }
376 : }
377 2 : else if (rename(fn1, fn2) != 0)
378 : {
379 0 : ereport(ERROR,
380 : (errcode_for_file_access(),
381 : errmsg("could not rename \"%s\" to \"%s\": %m", fn1, fn2)));
382 : }
383 :
384 4 : return true;
385 : }
386 :
387 :
388 : /* ------------------------------------
389 : * pg_file_unlink - old version
390 : *
391 : * The superuser() check here must be kept as the library might be upgraded
392 : * without the extension being upgraded, meaning that in pre-1.1 installations
393 : * these functions could be called by any user.
394 : */
395 : Datum
396 0 : pg_file_unlink(PG_FUNCTION_ARGS)
397 : {
398 : char *filename;
399 :
400 0 : requireSuperuser();
401 :
402 0 : filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
403 :
404 0 : if (access(filename, W_OK) < 0)
405 : {
406 0 : if (errno == ENOENT)
407 0 : PG_RETURN_BOOL(false);
408 : else
409 0 : ereport(ERROR,
410 : (errcode_for_file_access(),
411 : errmsg("file \"%s\" is not accessible: %m", filename)));
412 : }
413 :
414 0 : if (unlink(filename) < 0)
415 : {
416 0 : ereport(WARNING,
417 : (errcode_for_file_access(),
418 : errmsg("could not unlink file \"%s\": %m", filename)));
419 :
420 0 : PG_RETURN_BOOL(false);
421 : }
422 0 : PG_RETURN_BOOL(true);
423 : }
424 :
425 :
426 : /* ------------------------------------
427 : * pg_file_unlink_v1_1 - Version 1.1
428 : *
429 : * As of adminpack version 1.1, we no longer need to check if the user
430 : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
431 : * Users can then grant access to it based on their policies.
432 : *
433 : * Otherwise identical to pg_file_unlink (above).
434 : */
435 : Datum
436 10 : pg_file_unlink_v1_1(PG_FUNCTION_ARGS)
437 : {
438 : char *filename;
439 :
440 10 : filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
441 :
442 10 : if (access(filename, W_OK) < 0)
443 : {
444 4 : if (errno == ENOENT)
445 4 : PG_RETURN_BOOL(false);
446 : else
447 0 : ereport(ERROR,
448 : (errcode_for_file_access(),
449 : errmsg("file \"%s\" is not accessible: %m", filename)));
450 : }
451 :
452 6 : if (unlink(filename) < 0)
453 : {
454 0 : ereport(WARNING,
455 : (errcode_for_file_access(),
456 : errmsg("could not unlink file \"%s\": %m", filename)));
457 :
458 0 : PG_RETURN_BOOL(false);
459 : }
460 6 : PG_RETURN_BOOL(true);
461 : }
462 :
463 : /* ------------------------------------
464 : * pg_logdir_ls - Old version
465 : *
466 : * The superuser() check here must be kept as the library might be upgraded
467 : * without the extension being upgraded, meaning that in pre-1.1 installations
468 : * these functions could be called by any user.
469 : */
470 : Datum
471 0 : pg_logdir_ls(PG_FUNCTION_ARGS)
472 : {
473 0 : if (!superuser())
474 0 : ereport(ERROR,
475 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
476 : errmsg("only superuser can list the log directory")));
477 :
478 0 : return (pg_logdir_ls_internal(fcinfo));
479 : }
480 :
481 : /* ------------------------------------
482 : * pg_logdir_ls_v1_1 - Version 1.1
483 : *
484 : * As of adminpack version 1.1, we no longer need to check if the user
485 : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
486 : * Users can then grant access to it based on their policies.
487 : *
488 : * Otherwise identical to pg_logdir_ls (above).
489 : */
490 : Datum
491 0 : pg_logdir_ls_v1_1(PG_FUNCTION_ARGS)
492 : {
493 0 : return (pg_logdir_ls_internal(fcinfo));
494 : }
495 :
496 : static Datum
497 0 : pg_logdir_ls_internal(FunctionCallInfo fcinfo)
498 : {
499 0 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
500 : bool randomAccess;
501 : TupleDesc tupdesc;
502 : Tuplestorestate *tupstore;
503 : AttInMetadata *attinmeta;
504 : DIR *dirdesc;
505 : struct dirent *de;
506 : MemoryContext oldcontext;
507 :
508 0 : if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
509 0 : ereport(ERROR,
510 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
511 : errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'")));
512 :
513 : /* check to see if caller supports us returning a tuplestore */
514 0 : if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
515 0 : ereport(ERROR,
516 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
517 : errmsg("set-valued function called in context that cannot accept a set")));
518 0 : if (!(rsinfo->allowedModes & SFRM_Materialize))
519 0 : ereport(ERROR,
520 : (errcode(ERRCODE_SYNTAX_ERROR),
521 : errmsg("materialize mode required, but it is not allowed in this context")));
522 :
523 : /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
524 0 : oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
525 :
526 0 : tupdesc = CreateTemplateTupleDesc(2);
527 0 : TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
528 : TIMESTAMPOID, -1, 0);
529 0 : TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
530 : TEXTOID, -1, 0);
531 :
532 0 : randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
533 0 : tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
534 0 : rsinfo->returnMode = SFRM_Materialize;
535 0 : rsinfo->setResult = tupstore;
536 0 : rsinfo->setDesc = tupdesc;
537 :
538 0 : MemoryContextSwitchTo(oldcontext);
539 :
540 0 : attinmeta = TupleDescGetAttInMetadata(tupdesc);
541 :
542 0 : dirdesc = AllocateDir(Log_directory);
543 0 : while ((de = ReadDir(dirdesc, Log_directory)) != NULL)
544 : {
545 : char *values[2];
546 : HeapTuple tuple;
547 : char timestampbuf[32];
548 : char *field[MAXDATEFIELDS];
549 : char lowstr[MAXDATELEN + 1];
550 : int dtype;
551 : int nf,
552 : ftype[MAXDATEFIELDS];
553 : fsec_t fsec;
554 0 : int tz = 0;
555 : struct pg_tm date;
556 : DateTimeErrorExtra extra;
557 :
558 : /*
559 : * Default format: postgresql-YYYY-MM-DD_HHMMSS.log
560 : */
561 0 : if (strlen(de->d_name) != 32
562 0 : || strncmp(de->d_name, "postgresql-", 11) != 0
563 0 : || de->d_name[21] != '_'
564 0 : || strcmp(de->d_name + 28, ".log") != 0)
565 0 : continue;
566 :
567 : /* extract timestamp portion of filename */
568 0 : strcpy(timestampbuf, de->d_name + 11);
569 0 : timestampbuf[17] = '\0';
570 :
571 : /* parse and decode expected timestamp to verify it's OK format */
572 0 : if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
573 0 : continue;
574 :
575 0 : if (DecodeDateTime(field, ftype, nf,
576 : &dtype, &date, &fsec, &tz, &extra))
577 0 : continue;
578 :
579 : /* Seems the timestamp is OK; prepare and return tuple */
580 :
581 0 : values[0] = timestampbuf;
582 0 : values[1] = psprintf("%s/%s", Log_directory, de->d_name);
583 :
584 0 : tuple = BuildTupleFromCStrings(attinmeta, values);
585 :
586 0 : tuplestore_puttuple(tupstore, tuple);
587 : }
588 :
589 0 : FreeDir(dirdesc);
590 0 : return (Datum) 0;
591 : }
|