--- /dev/null
+/* contrib/adminpack/adminpack--1.0--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION adminpack UPDATE TO '1.1'" to load this file. \quit
+
+/* ***********************************************
+ * Administrative functions for PostgreSQL
+ * *********************************************** */
+
+/* generic file access functions */
+
+CREATE OR REPLACE FUNCTION pg_catalog.pg_file_write(text, text, bool)
+RETURNS bigint
+AS 'MODULE_PATHNAME', 'pg_file_write_v1_1'
+LANGUAGE C VOLATILE STRICT;
+
+REVOKE EXECUTE ON FUNCTION pg_catalog.pg_file_write(text, text, bool) FROM PUBLIC;
+
+CREATE OR REPLACE FUNCTION pg_catalog.pg_file_rename(text, text, text)
+RETURNS bool
+AS 'MODULE_PATHNAME', 'pg_file_rename_v1_1'
+LANGUAGE C VOLATILE;
+
+REVOKE EXECUTE ON FUNCTION pg_catalog.pg_file_rename(text, text, text) FROM PUBLIC;
+
+CREATE OR REPLACE FUNCTION pg_catalog.pg_file_rename(text, text)
+RETURNS bool
+AS 'SELECT pg_catalog.pg_file_rename($1, $2, NULL::pg_catalog.text);'
+LANGUAGE SQL VOLATILE STRICT;
+
+CREATE OR REPLACE FUNCTION pg_catalog.pg_file_unlink(text)
+RETURNS bool
+AS 'MODULE_PATHNAME', 'pg_file_unlink_v1_1'
+LANGUAGE C VOLATILE STRICT;
+
+REVOKE EXECUTE ON FUNCTION pg_catalog.pg_file_unlink(text) FROM PUBLIC;
+
+CREATE OR REPLACE FUNCTION pg_catalog.pg_logdir_ls()
+RETURNS setof record
+AS 'MODULE_PATHNAME', 'pg_logdir_ls_v1_1'
+LANGUAGE C VOLATILE STRICT;
+
+REVOKE EXECUTE ON FUNCTION pg_catalog.pg_logdir_ls() FROM PUBLIC;
+
+/* These functions are now in the backend and callers should update to use those */
+
+DROP FUNCTION pg_file_read(text, bigint, bigint);
+
+DROP FUNCTION pg_file_length(text);
+
+DROP FUNCTION pg_logfile_rotate();
#include
#include
+#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(pg_file_write);
+PG_FUNCTION_INFO_V1(pg_file_write_v1_1);
PG_FUNCTION_INFO_V1(pg_file_rename);
+PG_FUNCTION_INFO_V1(pg_file_rename_v1_1);
PG_FUNCTION_INFO_V1(pg_file_unlink);
+PG_FUNCTION_INFO_V1(pg_file_unlink_v1_1);
PG_FUNCTION_INFO_V1(pg_logdir_ls);
+PG_FUNCTION_INFO_V1(pg_logdir_ls_v1_1);
+
+static int64 pg_file_write_internal(text *file, text *data, bool replace);
+static bool pg_file_rename_internal(text *file1, text *file2, text *file3);
+static Datum pg_logdir_ls_internal(FunctionCallInfo fcinfo);
typedef struct
{
canonicalize_path(filename); /* filename can change length here */
+ /*
+ * Members of the 'pg_write_server_files' role are allowed to access any
+ * files on the server as the PG user, so no need to do any further checks
+ * here.
+ */
+ if (is_member_of_role(GetUserId(), DEFAULT_ROLE_WRITE_SERVER_FILES))
+ return filename;
+
+ /* User isn't a member of the default role, so check if it's allowable */
if (is_absolute_path(filename))
{
/* Disallow '/a/b/data/..' */
/* ------------------------------------
- * generic file handling functions
+ * pg_file_write - old version
+ *
+ * The superuser() check here must be kept as the library might be upgraded
+ * without the extension being upgraded, meaning that in pre-1.1 installations
+ * these functions could be called by any user.
*/
-
Datum
pg_file_write(PG_FUNCTION_ARGS)
{
- FILE *f;
- char *filename;
- text *data;
+ text *file = PG_GETARG_TEXT_PP(0);
+ text *data = PG_GETARG_TEXT_PP(1);
+ bool replace = PG_GETARG_BOOL(2);
int64 count = 0;
requireSuperuser();
- filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0), false);
- data = PG_GETARG_TEXT_PP(1);
+ count = pg_file_write_internal(file, data, replace);
+
+ PG_RETURN_INT64(count);
+}
+
+/* ------------------------------------
+ * pg_file_write_v1_1 - Version 1.1
+ *
+ * As of adminpack version 1.1, we no longer need to check if the user
+ * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
+ * Users can then grant access to it based on their policies.
+ *
+ * Otherwise identical to pg_file_write (above).
+ */
+Datum
+pg_file_write_v1_1(PG_FUNCTION_ARGS)
+{
+ text *file = PG_GETARG_TEXT_PP(0);
+ text *data = PG_GETARG_TEXT_PP(1);
+ bool replace = PG_GETARG_BOOL(2);
+ int64 count = 0;
+
+ count = pg_file_write_internal(file, data, replace);
+
+ PG_RETURN_INT64(count);
+}
- if (!PG_GETARG_BOOL(2))
+/* ------------------------------------
+ * pg_file_write_internal - Workhorse for pg_file_write functions.
+ *
+ * This handles the actual work for pg_file_write.
+ */
+int64
+pg_file_write_internal(text *file, text *data, bool replace)
+{
+ FILE *f;
+ char *filename;
+ int64 count = 0;
+
+ filename = convert_and_check_filename(file, false);
+
+ if (!replace)
{
struct stat fst;
(errcode_for_file_access(),
errmsg("could not write file \"%s\": %m", filename)));
- PG_RETURN_INT64(count);
+ return (count);
}
-
+/* ------------------------------------
+ * pg_file_rename - old version
+ *
+ * The superuser() check here must be kept as the library might be upgraded
+ * without the extension being upgraded, meaning that in pre-1.1 installations
+ * these functions could be called by any user.
+ */
Datum
pg_file_rename(PG_FUNCTION_ARGS)
{
- char *fn1,
- *fn2,
- *fn3;
- int rc;
+ text *file1;
+ text *file2;
+ text *file3;
+ bool result;
requireSuperuser();
if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
PG_RETURN_NULL();
- fn1 = convert_and_check_filename(PG_GETARG_TEXT_PP(0), false);
- fn2 = convert_and_check_filename(PG_GETARG_TEXT_PP(1), false);
+ file1 = PG_GETARG_TEXT_PP(0);
+ file2 = PG_GETARG_TEXT_PP(1);
+
if (PG_ARGISNULL(2))
+ file3 = NULL;
+ else
+ file3 = PG_GETARG_TEXT_PP(2);
+
+ result = pg_file_rename_internal(file1, file2, file3);
+
+ PG_RETURN_BOOL(result);
+}
+
+/* ------------------------------------
+ * pg_file_rename_v1_1 - Version 1.1
+ *
+ * As of adminpack version 1.1, we no longer need to check if the user
+ * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
+ * Users can then grant access to it based on their policies.
+ *
+ * Otherwise identical to pg_file_write (above).
+ */
+Datum
+pg_file_rename_v1_1(PG_FUNCTION_ARGS)
+{
+ text *file1;
+ text *file2;
+ text *file3;
+ bool result;
+
+ if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
+ PG_RETURN_NULL();
+
+ file1 = PG_GETARG_TEXT_PP(0);
+ file2 = PG_GETARG_TEXT_PP(1);
+
+ if (PG_ARGISNULL(2))
+ file3 = NULL;
+ else
+ file3 = PG_GETARG_TEXT_PP(2);
+
+ result = pg_file_rename_internal(file1, file2, file3);
+
+ PG_RETURN_BOOL(result);
+}
+
+/* ------------------------------------
+ * pg_file_rename_internal - Workhorse for pg_file_rename functions.
+ *
+ * This handles the actual work for pg_file_rename.
+ */
+bool
+pg_file_rename_internal(text *file1, text *file2, text *file3)
+{
+ char *fn1,
+ *fn2,
+ *fn3;
+ int rc;
+
+ fn1 = convert_and_check_filename(file1, false);
+ fn2 = convert_and_check_filename(file2, false);
+
+ if (file3 == NULL)
fn3 = 0;
else
- fn3 = convert_and_check_filename(PG_GETARG_TEXT_PP(2), false);
+ fn3 = convert_and_check_filename(file3, false);
if (access(fn1, W_OK) < 0)
{
(errcode_for_file_access(),
errmsg("file \"%s\" is not accessible: %m", fn1)));
- PG_RETURN_BOOL(false);
+ return false;
}
if (fn3 && access(fn2, W_OK) < 0)
(errcode_for_file_access(),
errmsg("file \"%s\" is not accessible: %m", fn2)));
- PG_RETURN_BOOL(false);
+ return false;
}
rc = access(fn3 ? fn3 : fn2, 2);
errmsg("could not rename \"%s\" to \"%s\": %m", fn1, fn2)));
}
- PG_RETURN_BOOL(true);
+ return true;
}
+/* ------------------------------------
+ * pg_file_unlink - old version
+ *
+ * The superuser() check here must be kept as the library might be upgraded
+ * without the extension being upgraded, meaning that in pre-1.1 installations
+ * these functions could be called by any user.
+ */
Datum
pg_file_unlink(PG_FUNCTION_ARGS)
{
}
+/* ------------------------------------
+ * pg_file_unlink_v1_1 - Version 1.1
+ *
+ * As of adminpack version 1.1, we no longer need to check if the user
+ * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
+ * Users can then grant access to it based on their policies.
+ *
+ * Otherwise identical to pg_file_unlink (above).
+ */
Datum
-pg_logdir_ls(PG_FUNCTION_ARGS)
+pg_file_unlink_v1_1(PG_FUNCTION_ARGS)
{
- FuncCallContext *funcctx;
- struct dirent *de;
- directory_fctx *fctx;
+ char *filename;
+
+ filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0), false);
+
+ if (access(filename, W_OK) < 0)
+ {
+ if (errno == ENOENT)
+ PG_RETURN_BOOL(false);
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("file \"%s\" is not accessible: %m", filename)));
+ }
+ if (unlink(filename) < 0)
+ {
+ ereport(WARNING,
+ (errcode_for_file_access(),
+ errmsg("could not unlink file \"%s\": %m", filename)));
+
+ PG_RETURN_BOOL(false);
+ }
+ PG_RETURN_BOOL(true);
+}
+
+/* ------------------------------------
+ * pg_logdir_ls - Old version
+ *
+ * The superuser() check here must be kept as the library might be upgraded
+ * without the extension being upgraded, meaning that in pre-1.1 installations
+ * these functions could be called by any user.
+ */
+Datum
+pg_logdir_ls(PG_FUNCTION_ARGS)
+{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
(errmsg("only superuser can list the log directory"))));
+ return (pg_logdir_ls_internal(fcinfo));
+}
+
+/* ------------------------------------
+ * pg_logdir_ls_v1_1 - Version 1.1
+ *
+ * As of adminpack version 1.1, we no longer need to check if the user
+ * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
+ * Users can then grant access to it based on their policies.
+ *
+ * Otherwise identical to pg_logdir_ls (above).
+ */
+Datum
+pg_logdir_ls_v1_1(PG_FUNCTION_ARGS)
+{
+ return (pg_logdir_ls_internal(fcinfo));
+}
+
+Datum
+pg_logdir_ls_internal(FunctionCallInfo fcinfo)
+{
+ FuncCallContext *funcctx;
+ struct dirent *de;
+ directory_fctx *fctx;
+
if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
test1test1
(1 row)
--- disallowed file paths
+-- disallowed file paths for non-superusers and users who are
+-- not members of pg_write_server_files
+CREATE ROLE regress_user1;
+GRANT pg_read_all_settings TO regress_user1;
+GRANT EXECUTE ON FUNCTION pg_file_write(text,text,bool) TO regress_user1;
+SET ROLE regress_user1;
SELECT pg_file_write('../test_file0', 'test0', false);
ERROR: path must be in or below the current directory
SELECT pg_file_write('/tmp/test_file0', 'test0', false);
SELECT pg_file_write(current_setting('data_directory') || '/../test_file4', 'test4', false);
ERROR: reference to parent directory ("..") not allowed
+RESET ROLE;
+REVOKE EXECUTE ON FUNCTION pg_file_write(text,text,bool) FROM regress_user1;
+REVOKE pg_read_all_settings FROM regress_user1;
+DROP ROLE regress_user1;
-- rename file
SELECT pg_file_rename('test_file1', 'test_file2');
pg_file_rename
CREATE USER regress_user1;
SET ROLE regress_user1;
SELECT pg_file_write('test_file0', 'test0', false);
-ERROR: only superuser may access generic file functions
+ERROR: permission denied for function pg_file_write
SELECT pg_file_rename('test_file0', 'test_file0');
-ERROR: only superuser may access generic file functions
+ERROR: permission denied for function pg_file_rename
CONTEXT: SQL function "pg_file_rename" statement 1
SELECT pg_file_unlink('test_file0');
-ERROR: only superuser may access generic file functions
+ERROR: permission denied for function pg_file_unlink
SELECT pg_logdir_ls();
-ERROR: only superuser can list the log directory
+ERROR: permission denied for function pg_logdir_ls
RESET ROLE;
DROP USER regress_user1;
-- no further tests for pg_logdir_ls() because it depends on the