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