LCOV - code coverage report
Current view: top level - contrib/adminpack - adminpack.c (source / functions) Hit Total Coverage
Test: PostgreSQL 12beta2 Lines: 75 169 44.4 %
Date: 2019-06-19 16:07:09 Functions: 15 22 68.2 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13