├── pg_restrict.conf ├── Makefile ├── sql └── pg_restrict.sql ├── expected └── pg_restrict.out ├── LICENSE ├── pg_restrict.vcxproj ├── README.md └── pg_restrict.c /pg_restrict.conf: -------------------------------------------------------------------------------- 1 | shared_preload_libraries = 'pg_restrict' 2 | pg_restrict.alter_system = on 3 | pg_restrict.copy_program = on 4 | pg_restrict.master_roles = 'role_restrict_1, postgres' 5 | pg_restrict.nonremovable_databases = 'db_restrict_a, db_restrict_b, postgres' 6 | pg_restrict.nonremovable_roles = 'role_restrict_a' 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MODULES = pg_restrict 2 | 3 | REGRESS_OPTS = --temp-instance=./tmp_check --temp-config=./pg_restrict.conf 4 | REGRESS = pg_restrict 5 | # Disabled because these tests require "shared_preload_libraries=pg_restrict", 6 | # which typical installcheck users do not have. 7 | NO_INSTALLCHECK = 1 8 | 9 | PG_CONFIG = pg_config 10 | PGXS := $(shell $(PG_CONFIG) --pgxs) 11 | include $(PGXS) 12 | 13 | astyle: 14 | astyle --style=bsd --indent=force-tab=4 --indent-switches --pad-oper --align-pointer=name --align-reference=name --remove-brackets --max-code-length=80 --break-after-logical --suffix=none --lineend=linux pg_restrict.c 15 | 16 | # But it can nonetheless be very helpful to run tests on preexisting 17 | # installation, allow to do so, but only if requested explicitly. 18 | check-force: 19 | $(pg_regress_installcheck) $(REGRESS_OPTS) $(REGRESS) 20 | -------------------------------------------------------------------------------- /sql/pg_restrict.sql: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | 3 | CREATE DATABASE db_restrict_1; 4 | 5 | CREATE ROLE role_restrict_1 SUPERUSER LOGIN; -- master role 6 | CREATE ROLE role_restrict_2 SUPERUSER LOGIN; 7 | CREATE ROLE role_restrict_3 SUPERUSER LOGIN; 8 | 9 | CREATE TABLE tmp_restrict_1 (a integer); 10 | INSERT INTO tmp_restrict_1 (a) VALUES(1),(2),(3); 11 | 12 | \connect - role_restrict_2 13 | 14 | CREATE DATABASE db_restrict_a; -- non-removable database 15 | 16 | DROP DATABASE db_restrict_a; -- failed 17 | DROP DATABASE db_restrict_1; -- succeed 18 | 19 | CREATE ROLE role_restrict_a; -- non-removable role 20 | 21 | DROP ROLE role_restrict_a; -- failed 22 | DROP ROLE role_restrict_3; -- succeed 23 | 24 | ALTER SYSTEM SET work_mem TO '2MB'; -- failed 25 | 26 | COPY tmp_restrict_1 TO PROGRAM 'gzip > /tmp/tmp_restrict_1.dat.gz'; -- failed 27 | 28 | COPY tmp_restrict_1 TO '/tmp/tmp_restrict_2.dat'; -- succeed 29 | 30 | \connect - role_restrict_1 31 | 32 | DROP DATABASE db_restrict_a; -- succeed 33 | 34 | DROP ROLE role_restrict_a; -- succeed 35 | 36 | ALTER SYSTEM SET work_mem TO '3MB'; -- succeed 37 | 38 | COPY tmp_restrict_1 TO PROGRAM 'gzip > /tmp/tmp_restrict_3.dat.gz'; -- succeed 39 | 40 | \connect - euler 41 | 42 | ALTER SYSTEM RESET work_mem; -- succeed 43 | 44 | DROP TABLE tmp_restrict_1; 45 | 46 | DROP ROLE role_restrict_1; -- succeed 47 | DROP ROLE role_restrict_2; -- succeed 48 | -------------------------------------------------------------------------------- /expected/pg_restrict.out: -------------------------------------------------------------------------------- 1 | \set VERBOSITY terse 2 | CREATE DATABASE db_restrict_1; 3 | CREATE ROLE role_restrict_1 SUPERUSER LOGIN; -- master role 4 | CREATE ROLE role_restrict_2 SUPERUSER LOGIN; 5 | CREATE ROLE role_restrict_3 SUPERUSER LOGIN; 6 | CREATE TABLE tmp_restrict_1 (a integer); 7 | INSERT INTO tmp_restrict_1 (a) VALUES(1),(2),(3); 8 | \connect - role_restrict_2 9 | CREATE DATABASE db_restrict_a; -- non-removable database 10 | DROP DATABASE db_restrict_a; -- failed 11 | ERROR: cannot drop database "db_restrict_a" 12 | DROP DATABASE db_restrict_1; -- succeed 13 | CREATE ROLE role_restrict_a; -- non-removable role 14 | DROP ROLE role_restrict_a; -- failed 15 | ERROR: cannot drop role "role_restrict_a" 16 | DROP ROLE role_restrict_3; -- succeed 17 | ALTER SYSTEM SET work_mem TO '2MB'; -- failed 18 | ERROR: cannot execute ALTER SYSTEM 19 | COPY tmp_restrict_1 TO PROGRAM 'gzip > /tmp/tmp_restrict_1.dat.gz'; -- failed 20 | ERROR: cannot execute COPY ... PROGRAM 21 | COPY tmp_restrict_1 TO '/tmp/tmp_restrict_2.dat'; -- succeed 22 | \connect - role_restrict_1 23 | DROP DATABASE db_restrict_a; -- succeed 24 | DROP ROLE role_restrict_a; -- succeed 25 | ALTER SYSTEM SET work_mem TO '3MB'; -- succeed 26 | COPY tmp_restrict_1 TO PROGRAM 'gzip > /tmp/tmp_restrict_3.dat.gz'; -- succeed 27 | \connect - euler 28 | ALTER SYSTEM RESET work_mem; -- succeed 29 | DROP TABLE tmp_restrict_1; 30 | DROP ROLE role_restrict_1; -- succeed 31 | DROP ROLE role_restrict_2; -- succeed 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Euler Taveira de Oliveira 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | * Neither the name of the Euler Taveira de Oliveira nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pg_restrict.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15.0 15 | {185AF5B6-C04A-4710-8330-18A4416BD225} 16 | Win32Proj 17 | 8.1 18 | 19 | 20 | 21 | DynamicLibrary 22 | true 23 | v141 24 | 25 | 26 | DynamicLibrary 27 | false 28 | v141 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | false 44 | 45 | 46 | false 47 | 48 | 49 | 50 | C:\postgres\pg103\include\server\port\win32_msvc;C:\postgres\pg103\include\server\port\win32;C:\postgres\pg103\include\server;C:\postgres\pg103\include;%(AdditionalIncludeDirectories) 51 | WIN32;_WINDOWS;__WINDOWS__;__WIN32__;EXEC_BACKEND;WIN32_STACK_RLIMIT=4194304;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_DEBUG;DEBUG=1%(PreprocessorDefinitions) 52 | false 53 | MultiThreadedDebugDLL 54 | 4018;4244;4273;4102;4090;4267;%(DisableSpecificWarnings) 55 | /MP %(AdditionalOptions) 56 | Default 57 | 58 | 59 | .\x64\Debug\pg_restrict.dll 60 | C:\postgres\pg103\lib;%(AdditionalLibraryDirectories) 61 | postgres.lib;libpgcommon.lib;libpgport.lib;%(AdditionalDependencies) 62 | MachineX64 63 | /ignore:4197 %(AdditionalOptions) 64 | 65 | 66 | false 67 | 68 | 69 | 70 | 71 | C:\postgres\pg103\include\server\port\win32_msvc;C:\postgres\pg103\include\server\port\win32;C:\postgres\pg103\include\server;C:\postgres\pg103\include;%(AdditionalIncludeDirectories) 72 | WIN32;_WINDOWS;__WINDOWS__;__WIN32__;EXEC_BACKEND;WIN32_STACK_RLIMIT=4194304;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_DEBUG;DEBUG=1%(PreprocessorDefinitions) 73 | false 74 | MultiThreadedDebugDLL 75 | 4018;4244;4273;4102;4090;4267;%(DisableSpecificWarnings) 76 | /MP %(AdditionalOptions) 77 | Default 78 | 79 | 80 | .\x64\Release\pg_restrict.dll 81 | C:\postgres\pg103\lib;%(AdditionalLibraryDirectories) 82 | postgres.lib;libpgcommon.lib;libpgport.lib;%(AdditionalDependencies) 83 | MachineX64 84 | /ignore:4197 %(AdditionalOptions) 85 | 86 | 87 | false 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | **pg_restrict** is an extension to restrict some SQL commands on [PostgreSQL](http://www.postgresql.org/). It introduces the master role concept that is similar to superuser. Even superusers can be forbid to drop databases and roles (if it is not a master role). 5 | 6 | Requirements 7 | ============ 8 | 9 | * PostgreSQL 9.3+ 10 | 11 | Build and Install 12 | ================= 13 | 14 | This extension is supported on [those platforms](http://www.postgresql.org/docs/current/static/supported-platforms.html) that PostgreSQL is. The installation steps depend on your operating system. 15 | 16 | You can also keep up with the latest fixes and features cloning the Git repository. 17 | 18 | ``` 19 | $ git clone https://github.com/eulerto/pg_restrict.git 20 | ``` 21 | 22 | Unix based Operating Systems 23 | ---------------------------- 24 | 25 | Before use this extension, you should build it and install it. 26 | 27 | ``` 28 | $ git clone https://github.com/eulerto/pg_restrict.git 29 | $ cd pg_restrict 30 | # Make sure your path includes the bin directory that contains the correct `pg_config` 31 | $ PATH=/path/to/pg/bin:$PATH 32 | $ make 33 | $ make install 34 | ``` 35 | 36 | Windows 37 | ------- 38 | 39 | There are several ways to build **pg_restrict** on Windows. If you are build PostgreSQL too, you can put **pg_restrict** directory inside contrib, change the contrib Makefile (variable SUBDIRS) and build it following the [Installation from Source Code on Windows](http://www.postgresql.org/docs/current/static/install-windows.html) instructions. However, if you already have PostgreSQL installed, it is also possible to compile **pg_restrict** out of the tree. Edit `pg_restrict.vcxproj` file and change `c:\postgres\pg113` to the PostgreSQL prefix directory. The next step is to open this project file in MS Visual Studio and compile it. Final step is to copy `pg_restrict.dll` to the `pg_config --pkglibdir` directory. 40 | 41 | Configuration 42 | ============= 43 | 44 | In order to function, this extension must be loaded via `shared_preload_libraries` in `postgresql.conf`. 45 | 46 | There are several configuration parameters that control the behavior of **pg_restrict**. The default behavior is to restrict drop databases `postgres`, `template1`, and `template0` and disallow removal of role `postgres`. Role `postgres` can drop any restricted database/role (because it is a master role, by default). 47 | 48 | * `pg_restrict.alter_system` (boolean): restrict ALTER SYSTEM command to master roles (`pg_restrict.master_roles` parameter). Default is _false_. 49 | * `pg_restrict.copy_program` (boolean): restrict COPY ... PROGRAM command to master roles (`pg_restrict.master_roles` parameter). Default is _false_. 50 | * `pg_restrict.master_roles` (string): Roles that are allowed to execute the restricted commands. If there is more than one role, separate them with comma. Default is _postgres_. 51 | * `pg_restrict.nonremovable_databases` (string): restrict DROP databases listed here to a master role (even if the current role is the database owner or superuser). Default is _postgres, template1, template0_. 52 | * `pg_restrict.nonremovable_roles` (string): restrict DROP roles listed here to a master role (even if the current role has CREATEROLE privilege or is a superuser). Default is _postgres_. 53 | 54 | These parameters are set in `postgresql.conf`. Typical usage might be: 55 | 56 | ``` 57 | shared_preload_libraries = 'pg_restrict' 58 | pg_restrict.alter_system = on 59 | pg_restrict.copy_program = off 60 | pg_restrict.master_roles = 'euler, admin' 61 | pg_restrict.nonremovable_databases = 'prod, bi, mydb, postgres, template1, template0' 62 | pg_restrict.nonremovable_roles = 'admin, euler, fulano' 63 | ``` 64 | 65 | Example 66 | ======= 67 | 68 | The following parameters are set in `postgresql.conf`. 69 | 70 | ``` 71 | shared_preload_libraries = 'pg_restrict' 72 | pg_restrict.master_roles = 'euler, postgres' 73 | pg_restrict.nonremovable_databases = 'prod, bi, postgres, template1, template0' 74 | pg_restrict.nonremovable_roles = 'admin, euler' 75 | ``` 76 | 77 | Let's create a new superuser called `fulano` and connect as `fulano`. Role `fulano` **is not** a master role. If `fulano` tries to drop a role `euler` (`euler` is listed as non-removable by non-master role), it errors out. Even though `fulano` creates a database called `prod`, it **can not** remove it because `prod` is listed as non-removable and `fulano` is not a master role. 78 | 79 | ``` 80 | postgres=# CREATE ROLE fulano SUPERUSER LOGIN; 81 | CREATE ROLE 82 | postgres=# \c - fulano 83 | You are now connected to database "euler" as user "fulano". 84 | postgres=# SELECT current_role; 85 | current_role 86 | -------------- 87 | fulano 88 | (1 row) 89 | 90 | postgres=# SHOW pg_restrict.master_roles; 91 | pg_restrict.master_roles 92 | -------------------------- 93 | euler, postgres 94 | (1 row) 95 | 96 | postgres=# SHOW pg_restrict.nonremovable_roles; 97 | pg_restrict.nonremovable_roles 98 | -------------------------------- 99 | admin, euler 100 | (1 row) 101 | 102 | postgres=# DROP ROLE euler; 103 | psql: ERRO: cannot drop role "euler" 104 | postgres=# CREATE DATABASE prod; 105 | CREATE DATABASE 106 | postgres=# SELECT current_role; 107 | current_role 108 | -------------- 109 | fulano 110 | (1 row) 111 | 112 | postgres=# SHOW pg_restrict.nonremovable_databases; 113 | pg_restrict.nonremovable_databases 114 | ------------------------------------------ 115 | prod, bi, postgres, template1, template0 116 | (1 row) 117 | 118 | postgres=# DROP DATABASE prod; 119 | psql: ERRO: cannot drop database "prod" 120 | ``` 121 | 122 | License 123 | ======= 124 | 125 | > Copyright (c) 2019, Euler Taveira de Oliveira 126 | > All rights reserved. 127 | 128 | > Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 129 | 130 | > Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 131 | 132 | > Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 133 | 134 | > Neither the name of the Euler Taveira de Oliveira nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 135 | 136 | > THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 137 | -------------------------------------------------------------------------------- /pg_restrict.c: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------- 2 | * 3 | * pg_restrict - restricts commands to master roles 4 | * 5 | * 6 | * Copyright (c) 2019, Euler Taveira 7 | * 8 | *---------------------------------------------------------------------- 9 | */ 10 | 11 | #include "postgres.h" 12 | #include "miscadmin.h" 13 | #include "nodes/pg_list.h" 14 | #include "parser/scansup.h" 15 | #include "tcop/utility.h" 16 | #include "utils/guc.h" 17 | #include "utils/memutils.h" 18 | 19 | #define PGR_DEFAULT_MASTER_ROLES "postgres" 20 | #define PGR_DEFAULT_NONREMOVABLE_DBS "postgres, template1, template0" 21 | #define PGR_DEFAULT_NONREMOVABLE_ROLES "postgres" 22 | 23 | PG_MODULE_MAGIC; 24 | 25 | #if PG_VERSION_NUM >= 90400 26 | /* whether to restrict ALTER SYSTEM */ 27 | bool alter_system; 28 | #endif 29 | /* wheter to restrict COPY ... PROGRAM */ 30 | bool copy_program; 31 | /* list of master roles (have no restrictions) */ 32 | char *masterroles = PGR_DEFAULT_MASTER_ROLES; 33 | /* whether to restrict DROP some databases */ 34 | char *nonremovabledbs = PGR_DEFAULT_NONREMOVABLE_DBS; 35 | /* whether to restrict DROP some roles */ 36 | char *nonremovableroles = PGR_DEFAULT_NONREMOVABLE_ROLES; 37 | 38 | static List *master_roles = NIL; 39 | static List *nonremovable_databases = NIL; 40 | static List *nonremovable_roles = NIL; 41 | 42 | 43 | void _PG_init(void); 44 | void _PG_fini(void); 45 | 46 | /* Saved hook value in case of unload */ 47 | static ProcessUtility_hook_type prev_ProcessUtility = NULL; 48 | 49 | static bool check_nonremovable_databases(char **newval, void **extra, 50 | GucSource source); 51 | static void assign_nonremovable_databases(const char *newval, void *extra); 52 | static bool check_nonremovable_roles(char **newval, void **extra, 53 | GucSource source); 54 | static void assign_nonremovable_roles(const char *newval, void *extra); 55 | static bool check_master_roles(char **newval, void **extra, 56 | GucSource source); 57 | static void assign_master_roles(const char *newval, void *extra); 58 | 59 | #if PG_VERSION_NUM >= 130000 60 | static void pgr_ProcessUtility(PlannedStmt *pstmt, const char *queryString, 61 | ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, 62 | DestReceiver *dest, QueryCompletion *qc); 63 | #elif PG_VERSION_NUM >= 100000 64 | static void pgr_ProcessUtility(PlannedStmt *pstmt, const char *queryString, 65 | ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, 66 | DestReceiver *dest, char *completionTag); 67 | #else 68 | static void pgr_ProcessUtility(Node *pstmt, const char *queryString, 69 | ProcessUtilityContext context, ParamListInfo params, 70 | DestReceiver *dest, char *completionTag); 71 | #endif 72 | static bool split_string_into_list(char *rawstring, char separator, 73 | List **namelist); 74 | 75 | void 76 | _PG_init(void) 77 | { 78 | /* 79 | * Define (or redefine) custom GUC variable. 80 | */ 81 | #if PG_VERSION_NUM >= 90400 82 | DefineCustomBoolVariable("pg_restrict.alter_system", 83 | "Roles cannot use ALTER SYSTEM unless it is listed as master role.", 84 | NULL, 85 | &alter_system, 86 | false, 87 | PGC_SIGHUP, 0, 88 | NULL, 89 | NULL, 90 | NULL); 91 | #endif 92 | 93 | DefineCustomBoolVariable("pg_restrict.copy_program", 94 | "Roles (even superusers) cannot use COPY ... PROGRAM unless it is listed as master role.", 95 | NULL, 96 | ©_program, 97 | false, 98 | PGC_SIGHUP, 0, 99 | NULL, 100 | NULL, 101 | NULL); 102 | 103 | DefineCustomStringVariable("pg_restrict.master_roles", 104 | "Roles that are allowed to execute restricted commands.", 105 | NULL, 106 | &masterroles, 107 | PGR_DEFAULT_MASTER_ROLES, 108 | PGC_POSTMASTER, 0, 109 | check_master_roles, 110 | assign_master_roles, 111 | NULL); 112 | 113 | DefineCustomStringVariable("pg_restrict.nonremovable_databases", 114 | "Roles (even superusers) cannot drop these databases unless it is listed as master role.", 115 | NULL, 116 | &nonremovabledbs, 117 | PGR_DEFAULT_NONREMOVABLE_DBS, 118 | PGC_SIGHUP, 0, 119 | check_nonremovable_databases, 120 | assign_nonremovable_databases, 121 | NULL); 122 | 123 | DefineCustomStringVariable("pg_restrict.nonremovable_roles", 124 | "Roles (even superusers) cannot drop these roles unless it is listed as master role.", 125 | NULL, 126 | &nonremovableroles, 127 | PGR_DEFAULT_NONREMOVABLE_ROLES, 128 | PGC_SIGHUP, 0, 129 | check_nonremovable_roles, 130 | assign_nonremovable_roles, 131 | NULL); 132 | 133 | EmitWarningsOnPlaceholders("pg_restrict"); 134 | 135 | /* 136 | * Install hook. 137 | */ 138 | prev_ProcessUtility = ProcessUtility_hook; 139 | ProcessUtility_hook = pgr_ProcessUtility; 140 | } 141 | 142 | void 143 | _PG_fini(void) 144 | { 145 | /* 146 | * Uninstall hook. 147 | */ 148 | ProcessUtility_hook = prev_ProcessUtility; 149 | } 150 | 151 | /* 152 | * ProcessUtility hook 153 | */ 154 | #if PG_VERSION_NUM >= 130000 155 | static void 156 | pgr_ProcessUtility(PlannedStmt *pstmt, const char *queryString, 157 | ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, 158 | DestReceiver *dest, QueryCompletion *qc) 159 | { 160 | Node *pst = (Node *) pstmt->utilityStmt; 161 | #elif PG_VERSION_NUM >= 100000 162 | static void 163 | pgr_ProcessUtility(PlannedStmt *pstmt, const char *queryString, 164 | ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, 165 | DestReceiver *dest, char *completionTag) 166 | { 167 | Node *pst = (Node *) pstmt->utilityStmt; 168 | #else 169 | static void 170 | pgr_ProcessUtility(Node *pst, const char *queryString, 171 | ProcessUtilityContext context, ParamListInfo params, 172 | DestReceiver *dest, char *completionTag) 173 | { 174 | #endif 175 | 176 | #if PG_VERSION_NUM >= 90500 177 | char *current_rolename = GetUserNameFromId(GetUserId(), false); 178 | #else 179 | char *current_rolename = GetUserNameFromId(GetUserId()); 180 | #endif 181 | 182 | if (IsA(pst, DropdbStmt)) 183 | { 184 | DropdbStmt *stmt = (DropdbStmt *) pst; 185 | ListCell *lc; 186 | 187 | /* 188 | * Only master roles can drop databases listed as non-removable databases 189 | */ 190 | foreach(lc, nonremovable_databases) 191 | { 192 | if (strcmp(lfirst(lc), stmt->dbname) == 0) 193 | { 194 | bool is_master = false; 195 | ListCell *tc; 196 | 197 | foreach(tc, master_roles) 198 | { 199 | if (strcmp(lfirst(tc), current_rolename) == 0) 200 | { 201 | is_master = true; 202 | break; 203 | } 204 | } 205 | 206 | if (!is_master) 207 | ereport(ERROR, 208 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), 209 | errmsg("cannot drop database \"%s\"", stmt->dbname))); 210 | } 211 | } 212 | } 213 | else if (IsA(pst, DropRoleStmt)) 214 | { 215 | DropRoleStmt *stmt = (DropRoleStmt *) pst; 216 | ListCell *lc; 217 | 218 | foreach(lc, stmt->roles) 219 | { 220 | #if PG_VERSION_NUM >= 90500 221 | RoleSpec *rolspec = lfirst(lc); 222 | char *dropped_role = rolspec->rolename; 223 | #else 224 | const char *dropped_role = strVal(lfirst(lc)); 225 | #endif 226 | ListCell *tc; 227 | 228 | /* 229 | * Only master roles can drop roles listed as non-removable roles 230 | */ 231 | foreach(tc, nonremovable_roles) 232 | { 233 | if (strcmp(lfirst(tc), dropped_role) == 0) 234 | { 235 | bool is_master = false; 236 | ListCell *ms; 237 | 238 | foreach(ms, master_roles) 239 | { 240 | if (strcmp(lfirst(ms), current_rolename) == 0) 241 | { 242 | is_master = true; 243 | break; 244 | } 245 | } 246 | 247 | if (!is_master) 248 | ereport(ERROR, 249 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), 250 | errmsg("cannot drop role \"%s\"", dropped_role))); 251 | } 252 | } 253 | } 254 | } 255 | #if PG_VERSION_NUM >= 90400 256 | else if (IsA(pst, AlterSystemStmt) && alter_system) 257 | { 258 | bool is_master = false; 259 | ListCell *lc; 260 | 261 | foreach(lc, master_roles) 262 | { 263 | if (strcmp(lfirst(lc), current_rolename) == 0) 264 | { 265 | is_master = true; 266 | break; 267 | } 268 | } 269 | 270 | /* 271 | * Only master roles can execute ALTER SYSTEM 272 | */ 273 | if (!is_master) 274 | ereport(ERROR, 275 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), 276 | errmsg("cannot execute ALTER SYSTEM"))); 277 | } 278 | #endif /* AlterSystemStmt >= 9.4 */ 279 | else if (IsA(pst, CopyStmt) && copy_program) 280 | { 281 | CopyStmt *stmt = (CopyStmt *) pst; 282 | bool is_master = false; 283 | ListCell *lc; 284 | 285 | /* COPY ... PROGRAM is new in 9.3 */ 286 | if (stmt->is_program) 287 | { 288 | foreach(lc, master_roles) 289 | { 290 | if (strcmp(lfirst(lc), current_rolename) == 0) 291 | { 292 | is_master = true; 293 | break; 294 | } 295 | } 296 | 297 | /* 298 | * Only master roles can execute COPY ... PROGRAM 299 | */ 300 | if (!is_master) 301 | ereport(ERROR, 302 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), 303 | errmsg("cannot execute COPY ... PROGRAM"))); 304 | } 305 | } 306 | 307 | /* 308 | * Fallback to normal process, be it the previous hook loaded 309 | * or the in-core code path if the previous hook does not exist. 310 | */ 311 | #if PG_VERSION_NUM >= 130000 312 | if (prev_ProcessUtility) 313 | prev_ProcessUtility(pstmt, queryString, 314 | context, params, queryEnv, 315 | dest, qc); 316 | else 317 | standard_ProcessUtility(pstmt, queryString, 318 | context, params, queryEnv, 319 | dest, qc); 320 | #elif PG_VERSION_NUM >= 100000 321 | if (prev_ProcessUtility) 322 | prev_ProcessUtility(pstmt, queryString, 323 | context, params, queryEnv, 324 | dest, completionTag); 325 | else 326 | standard_ProcessUtility(pstmt, queryString, 327 | context, params, queryEnv, 328 | dest, completionTag); 329 | #else 330 | if (prev_ProcessUtility) 331 | prev_ProcessUtility(pst, queryString, 332 | context, params, 333 | dest, completionTag); 334 | else 335 | standard_ProcessUtility(pst, queryString, 336 | context, params, 337 | dest, completionTag); 338 | #endif 339 | } 340 | 341 | static bool 342 | check_nonremovable_databases(char **newval, void **extra, GucSource source) 343 | { 344 | char *rawstring; 345 | List *newnrdbs = NIL; 346 | List *ltmp; 347 | ListCell *lc; 348 | MemoryContext oldctx; 349 | 350 | rawstring = pstrdup(*newval); 351 | 352 | if (!split_string_into_list(rawstring, ',', <mp)) 353 | { 354 | /* syntax error in list */ 355 | GUC_check_errdetail("List syntax is invalid."); 356 | pfree(rawstring); 357 | return false; 358 | } 359 | 360 | oldctx = MemoryContextSwitchTo(TopMemoryContext); 361 | 362 | foreach(lc, ltmp) 363 | { 364 | char *t = pstrdup((char *) lfirst(lc)); 365 | newnrdbs = lappend(newnrdbs, t); 366 | } 367 | 368 | list_free(nonremovable_databases); 369 | nonremovable_databases = newnrdbs; 370 | 371 | MemoryContextSwitchTo(oldctx); 372 | 373 | pfree(rawstring); 374 | 375 | return true; 376 | } 377 | 378 | static void 379 | assign_nonremovable_databases(const char *newval, void *extra) 380 | { 381 | } 382 | 383 | static bool 384 | check_nonremovable_roles(char **newval, void **extra, GucSource source) 385 | { 386 | char *rawstring; 387 | List *newnrroles = NIL; 388 | List *ltmp; 389 | ListCell *lc; 390 | MemoryContext oldctx; 391 | 392 | rawstring = pstrdup(*newval); 393 | 394 | if (!split_string_into_list(rawstring, ',', <mp)) 395 | { 396 | /* syntax error in list */ 397 | GUC_check_errdetail("List syntax is invalid."); 398 | pfree(rawstring); 399 | return false; 400 | } 401 | 402 | oldctx = MemoryContextSwitchTo(TopMemoryContext); 403 | 404 | foreach(lc, ltmp) 405 | { 406 | char *t = pstrdup((char *) lfirst(lc)); 407 | newnrroles = lappend(newnrroles, t); 408 | } 409 | 410 | list_free(nonremovable_roles); 411 | nonremovable_roles = newnrroles; 412 | 413 | MemoryContextSwitchTo(oldctx); 414 | 415 | pfree(rawstring); 416 | 417 | return true; 418 | } 419 | 420 | static void 421 | assign_nonremovable_roles(const char *newval, void *extra) 422 | { 423 | } 424 | 425 | static bool 426 | check_master_roles(char **newval, void **extra, GucSource source) 427 | { 428 | char *rawstring; 429 | List *newmasterroles = NIL; 430 | List *ltmp; 431 | ListCell *lc; 432 | MemoryContext oldctx; 433 | 434 | rawstring = pstrdup(*newval); 435 | 436 | if (!split_string_into_list(rawstring, ',', <mp)) 437 | { 438 | /* syntax error in list */ 439 | GUC_check_errdetail("List syntax is invalid."); 440 | pfree(rawstring); 441 | return false; 442 | } 443 | 444 | oldctx = MemoryContextSwitchTo(TopMemoryContext); 445 | 446 | foreach(lc, ltmp) 447 | { 448 | char *t = pstrdup((char *) lfirst(lc)); 449 | newmasterroles = lappend(newmasterroles, t); 450 | } 451 | 452 | list_free(master_roles); 453 | master_roles = newmasterroles; 454 | 455 | MemoryContextSwitchTo(oldctx); 456 | 457 | pfree(rawstring); 458 | 459 | return true; 460 | } 461 | 462 | static void 463 | assign_master_roles(const char *newval, void *extra) 464 | { 465 | } 466 | 467 | /* 468 | * This function is a copy of split_string_into_list. It is here because it was 469 | * introduced as a bugfix. 9.3.24, 9,4,19, 9.5.14, 9.6.10, and 10.5 already 470 | * contains it but prior minor versions don't. Since we want to support stable 471 | * versions, it is included here as is. 472 | */ 473 | static bool 474 | split_string_into_list(char *rawstring, char separator, 475 | List **namelist) 476 | { 477 | char *nextp = rawstring; 478 | bool done = false; 479 | 480 | *namelist = NIL; 481 | 482 | while (scanner_isspace(*nextp)) 483 | nextp++; /* skip leading whitespace */ 484 | 485 | if (*nextp == '\0') 486 | return true; /* allow empty string */ 487 | 488 | /* At the top of the loop, we are at start of a new identifier. */ 489 | do 490 | { 491 | char *curname; 492 | char *endp; 493 | 494 | if (*nextp == '"') 495 | { 496 | /* Quoted name --- collapse quote-quote pairs */ 497 | curname = nextp + 1; 498 | for (;;) 499 | { 500 | endp = strchr(nextp + 1, '"'); 501 | if (endp == NULL) 502 | return false; /* mismatched quotes */ 503 | if (endp[1] != '"') 504 | break; /* found end of quoted name */ 505 | /* Collapse adjacent quotes into one quote, and look again */ 506 | memmove(endp, endp + 1, strlen(endp)); 507 | nextp = endp; 508 | } 509 | /* endp now points at the terminating quote */ 510 | nextp = endp + 1; 511 | } 512 | else 513 | { 514 | /* Unquoted name --- extends to separator or whitespace */ 515 | curname = nextp; 516 | while (*nextp && *nextp != separator && 517 | !scanner_isspace(*nextp)) 518 | nextp++; 519 | endp = nextp; 520 | if (curname == nextp) 521 | return false; /* empty unquoted name not allowed */ 522 | } 523 | 524 | while (scanner_isspace(*nextp)) 525 | nextp++; /* skip trailing whitespace */ 526 | 527 | if (*nextp == separator) 528 | { 529 | nextp++; 530 | while (scanner_isspace(*nextp)) 531 | nextp++; /* skip leading whitespace for next */ 532 | /* we expect another name, so done remains false */ 533 | } 534 | else if (*nextp == '\0') 535 | done = true; 536 | else 537 | return false; /* invalid syntax */ 538 | 539 | /* Now safe to overwrite separator with a null */ 540 | *endp = '\0'; 541 | 542 | /* 543 | * Finished isolating current name --- add it to list 544 | */ 545 | *namelist = lappend(*namelist, curname); 546 | 547 | /* Loop back if we didn't reach end of string */ 548 | } 549 | while (!done); 550 | 551 | return true; 552 | } 553 | --------------------------------------------------------------------------------