pax_global_header00006660000000000000000000000064141402022120014476gustar00rootroot0000000000000052 comment=43d7739c85432478816557bb39cf230207759a4d pgextwlist-1.12/000077500000000000000000000000001414020221200136335ustar00rootroot00000000000000pgextwlist-1.12/.gitignore000066400000000000000000000000411414020221200156160ustar00rootroot00000000000000*.o pgextwlist.so .deps .vagrant pgextwlist-1.12/Makefile000066400000000000000000000021761414020221200153010ustar00rootroot00000000000000short_ver = $(shell git describe --match "v[0-9]*" --abbrev=4 HEAD | cut -d- -f1 | sed 's/^v//') long_ver = $(shell (git describe --tags --long '--match=v*' 2>/dev/null || echo $(short_ver)-0-unknown) | cut -c2-) MODULE_big = pgextwlist OBJS = utils.o pgextwlist.o DOCS = README.md REGRESS = pgextwlist crossuser RPM_MINOR_VERSION_SUFFIX ?= PG_CONFIG = pg_config PGXS = $(shell $(PG_CONFIG) --pgxs) include $(PGXS) DEBUILD_ROOT = /tmp/pgextwlist deb: mkdir -p $(DEBUILD_ROOT) && rm -rf $(DEBUILD_ROOT)/* rsync -Ca --exclude=build/* ./ $(DEBUILD_ROOT)/ cd $(DEBUILD_ROOT) && make -f debian/rules orig cd $(DEBUILD_ROOT) && debuild -us -uc -sa cp -a /tmp/pgextwlist_* /tmp/postgresql-9.* build/ rpm: git archive --output=pgextwlist-rpm-src.tar.gz --prefix=pgextwlist/ HEAD rpmbuild -bb pgextwlist.spec \ --define '_sourcedir $(CURDIR)' \ --define 'package_prefix $(package_prefix)' \ --define 'pkglibdir $(shell $(PG_CONFIG) --pkglibdir)' \ --define 'major_version $(short_ver)' \ --define 'minor_version $(subst -,.,$(subst $(short_ver)-,,$(long_ver)))$(RPM_MINOR_VERSION_SUFFIX)' $(RM) pgextwlist-rpm-src.tar.gz pgextwlist-1.12/README.md000066400000000000000000000162201414020221200151130ustar00rootroot00000000000000# PostgreSQL Extension Whitelist This extension implements extension whitelisting, and will actively prevent users from installing extensions not in the provided list. Also, this extension implements a form of `sudo` facility in that the whitelisted extensions will get installed as if *superuser*. Privileges are dropped before handing the control back to the user. The operations `CREATE EXTENSION`, `DROP EXTENSION` and `ALTER EXTENSION ... UPDATE` are run by *superuser*. The `ALTER EXTENSION ... ADD|DROP` command is intentionnaly not supported so as not to allow users to modify an already installed extension. That means that it's not currently possible to `CREATE EXTENSION ... FROM 'unpackaged';`. Note that the extension script is running as if run by a stored procedure owned by your *bootstrap superuser* and with `SECURITY DEFINER`, meaning that the extension and all its objects are owned by this *superuser*. [![Build Status](https://travis-ci.org/dimitri/pgextwlist.svg?branch=master)](https://travis-ci.org/dimitri/pgextwlist) ## Licence The `pgextwlist` PostgreSQL extension is released under [The PostgreSQL Licence](http://www.postgresql.org/about/licence/), a liberal Open Source license, similar to the BSD or MIT licenses. ## Install 1. Install the server development packages (on Ubuntu, this would look like `apt-get install postgresql-server-dev-all`) 2. then: make sudo make install This will generate a `pgextwlist.so` shared library that you will have to install in `pg_config --pkglibdir`/plugins so that your backend loads it automatically. ## Setup You need to define the list of extensions that are whitelisted, the user that performs the extension installing, and the error behavior. * `local_preload_libraries` Add `pgextwlist` to the `local_preload_libraries` setting. Don't forget to add the module in the `$plugin` directory. * `custom_variable_classes` Add `extwlist` to the `custom_variable_classes` setting if you're using 9.1, in 9.2 this setting disappeared. * `extwlist.extensions` List of extensions allowed for installation. * `extwlist.custom_path` Filesystem path where to look for *custom scripts*. ## Usage That's quite simple: $ edit postgresql.conf to add local_preload_libraries, custom_variable_classes and extwlist.extensions dim=# show extwlist.extensions; show extwlist.extensions; extwlist.extensions --------------------- hstore,cube (1 row) dim=# create extension foo; create extension foo; ERROR: extension "foo" is not whitelisted DETAIL: Installing the extension "foo" failed, because it is not on the whitelist of user-installable extensions. HINT: Your system administrator has allowed users to install certain extensions. See: SHOW extwlist.extensions; dim=# create extension hstore; create extension hstore; WARNING: => is deprecated as an operator name DETAIL: This name may be disallowed altogether in future versions of PostgreSQL. CREATE EXTENSION dim=# \dx \dx List of installed extensions Name | Version | Schema | Description ---------+---------+------------+-------------------------------------------------- hstore | 1.0 | public | data type for storing sets of (key, value) pairs plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language (2 rows) Even if you're not superuser: dim=> select rolsuper from pg_roles where rolname = current_user; select rolsuper from pg_roles where rolname = current_user; rolsuper ---------- f (1 row) dim=> create extension hstore; create extension hstore; WARNING: => is deprecated as an operator name DETAIL: This name may be disallowed altogether in future versions of PostgreSQL. CREATE EXTENSION dim=> create extension earthdistance; create extension earthdistance; ERROR: extension "earthdistance" is not whitelisted DETAIL: Installing the extension "earthdistance" failed, because it is not on the whitelist of user-installable extensions. HINT: Your system administrator has allowed users to install certain extensions. SHOW extwlist.extensions; dim=> \dx \dx List of installed extensions Name | Version | Schema | Description ---------+---------+------------+-------------------------------------------------- hstore | 1.0 | public | data type for storing sets of (key, value) pairs plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language (2 rows) dim=> drop extension hstore; drop extension hstore; DROP EXTENSION ## Custom Scripts Some extensions are installing objects that only the *superuser* can make use of by default, it's then a good idea to tweak permissions and grant usage to the *current_user* or even the *database owner*, depending. The custom scripts feature allows to do that by providing scripts to be run around the execution of the extension's script itself. #### create extension custom scripts For the creation of extension `extname` version `1.0` the following scripts will be used when they do exist, as shown here: #. `${extwlist.custom_path}/extname/before--1.0.sql` #. `${extwlist.custom_path}/extname/before-create.sql`, only when the previous one, specific to the version being installed, does not exists. #. The `CREATE EXTENSION` command now runs normally #. `${extwlist.custom_path}/extname/after--1.0.sql` #. `${extwlist.custom_path}/extname/after-create.sql` #### alter extension update custom scripts For the update of extension `extname` from version `1.0` to version `1.1` the following scripts will be used when they do exist, as shown here: #. `${extwlist.custom_path}/extname/before--1.0--1.1.sql` #. `${extwlist.custom_path}/extname/before-update.sql`, only when the previous one does not exists. #. The `ALTER EXTENSION UPDATE` command now runs normally #. `${extwlist.custom_path}/extname/after--1.0--1.1.sql` #. `${extwlist.custom_path}/extname/after-update.sql` only when the previous one, specific to the versions being considered, does not exists. #### custom scripts templating Before executing them, the *extwlist* extension applies the following substitions to the *custom scripts*: - any line that begins with `\echo` is removed, - the literal `@extschema@` is unconditionnaly replaced by the current schema being used to create the extension objects, - the literal `@current_user@` is replaced by the name of the current user, - the literal `@database_owner@` is replaced by the name of the current database owner. Tip: remember that you can execute `DO` blocks if you need dynamic sql. ## Internals The whitelisting works by overloading the `ProcessUtility_hook` and gaining control each time a utility statement is issued. When this statement is a `CREATE EXTENSION`, the extension's name is extracted from the `parsetree` and checked against the whitelist. *Superuser* is obtained as in the usual `SECURITY DEFINER` case, except hard coded to target the *bootstrap user*. pgextwlist-1.12/Vagrantfile000066400000000000000000000004251414020221200160210ustar00rootroot00000000000000# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure("2") do |config| config.vm.box = "wheezy64" config.vm.provision :shell, :path => "bootstrap.sh" end pgextwlist-1.12/bootstrap.sh000066400000000000000000000004351414020221200162060ustar00rootroot00000000000000#!/usr/bin/env bash echo "deb http://apt.postgresql.org/pub/repos/apt/ wheezy-pgdg main" > /etc/apt/sources.list.d/pgdg.list wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - apt-get update apt-get install -y postgresql-server-dev-all devscripts pgextwlist-1.12/build/000077500000000000000000000000001414020221200147325ustar00rootroot00000000000000pgextwlist-1.12/build/.gitignore000066400000000000000000000001061414020221200167170ustar00rootroot00000000000000# Ignore everything in this directory * # Except this file !.gitignorepgextwlist-1.12/debian/000077500000000000000000000000001414020221200150555ustar00rootroot00000000000000pgextwlist-1.12/debian/changelog000066400000000000000000000066451414020221200167420ustar00rootroot00000000000000pgextwlist (1.12-1) unstable; urgency=medium * New version with PG13 support, thanks Ronan Dunklau! -- Christoph Berg Tue, 02 Nov 2021 10:26:57 +0100 pgextwlist (1.11-4) unstable; urgency=medium * Work around extension_destdir bug with missing extension directory. -- Christoph Berg Fri, 09 Oct 2020 16:22:49 +0200 pgextwlist (1.11-3) unstable; urgency=medium * R³: no. -- Christoph Berg Fri, 09 Oct 2020 10:50:24 +0200 pgextwlist (1.11-2) unstable; urgency=medium * Upload for PostgreSQL 13. * Use dh --with pgxs_loop and run tests at build time as well. * debian/tests: Use pg_buildext installcheck. -- Christoph Berg Tue, 06 Oct 2020 15:13:07 +0200 pgextwlist (1.11-1) unstable; urgency=medium * New version with PG13 support. * Source format 3.0 (quilt). * DH 13, bump S-V. -- Christoph Berg Sun, 24 May 2020 20:53:50 +0200 pgextwlist (1.10-1) unstable; urgency=medium [ Mika Eloranta ] * Use explicit collation for @@ substibutions. [ Christoph Berg ] * Include custom scripts in regression tests. -- Christoph Berg Tue, 29 Oct 2019 14:48:29 +0100 pgextwlist (1.9-1) unstable; urgency=medium * New upstream version with PostgreSQL 12 support. -- Christoph Berg Wed, 05 Jun 2019 12:44:56 +0200 pgextwlist (1.8-2) unstable; urgency=medium * Recompile against new heap_getattr() API in PostgreSQL 11.2. -- Christoph Berg Tue, 12 Feb 2019 15:08:44 +0100 pgextwlist (1.8-1) unstable; urgency=medium * Upload for PostgreSQL 11. * Update PostgreSQL team address. -- Christoph Berg Fri, 12 Oct 2018 13:23:58 +0200 pgextwlist (1.7-1) unstable; urgency=medium * New upstream version with PostgreSQL 11 support. -- Christoph Berg Mon, 23 Jul 2018 11:53:24 +0200 pgextwlist (1.6-1) unstable; urgency=medium * New upstream version with PostgreSQL 10 support, patch by Oskari Saarenmaa. (Closes: #876851) * Update Dimitri's email address. -- Christoph Berg Sun, 15 Oct 2017 13:21:54 +0200 pgextwlist (1.5-1) unstable; urgency=medium * New upstream version with 9.6 support. -- Christoph Berg Tue, 27 Sep 2016 18:39:03 +0200 pgextwlist (1.4-1) unstable; urgency=medium * New upstream version with 9.5 support. (Closes: #811135) -- Christoph Berg Tue, 02 Feb 2016 12:56:49 +0100 pgextwlist (1.3-3) unstable; urgency=medium * Upload to unstable for 9.4. -- Christoph Berg Sun, 27 Jul 2014 11:13:25 +0200 pgextwlist (1.3-2) experimental; urgency=medium * B-D on pg-common 158 to build against 9.3 and 9.4. -- Christoph Berg Sun, 06 Jul 2014 17:11:21 +0200 pgextwlist (1.3-1) unstable; urgency=medium * Set team as Maintainer. * Use 9.1+ in debian/pgversions. * Update regression tests. -- Christoph Berg Mon, 16 Jun 2014 18:30:07 +0200 pgextwlist (1.2-1) unstable; urgency=medium * Add regression tests and run them using autopkgtest. * Include the plugins/pgextwlist.so symlink in the package. * Add watch file. -- Christoph Berg Wed, 05 Feb 2014 15:25:54 +0100 pgextwlist (1.1-1) unstable; urgency=low * Initial release -- Dimitri Fontaine Mon, 30 Dec 2013 18:36:43 +0400 pgextwlist-1.12/debian/control000066400000000000000000000017451414020221200164670ustar00rootroot00000000000000Source: pgextwlist Priority: optional Maintainer: Debian PostgreSQL Maintainers Uploaders: Dimitri Fontaine , Christoph Berg , Build-Depends: debhelper-compat (= 13), postgresql-all (>= 219~), Standards-Version: 4.5.0 Rules-Requires-Root: no Section: database Homepage: https://github.com/dimitri/pgextwlist Vcs-Git: https://github.com/dimitri/pgextwlist.git Vcs-Browser: https://github.com/dimitri/pgextwlist Package: postgresql-14-pgextwlist Section: libs Architecture: any Depends: postgresql-14, ${misc:Depends}, ${shlibs:Depends}, Description: PostgreSQL Extension Whitelisting This extension implements extension whitelisting, and will actively prevent users from installing extensions not in the provided list. Also, this extension implements a form of sudo facility in that the whitelisted extensions will get installed as if superuser. Privileges are dropped before handing the control back to the user. pgextwlist-1.12/debian/control.in000066400000000000000000000017631414020221200170740ustar00rootroot00000000000000Source: pgextwlist Priority: optional Maintainer: Debian PostgreSQL Maintainers Uploaders: Dimitri Fontaine , Christoph Berg , Build-Depends: debhelper-compat (= 13), postgresql-all (>= 219~), Standards-Version: 4.5.0 Rules-Requires-Root: no Section: database Homepage: https://github.com/dimitri/pgextwlist Vcs-Git: https://github.com/dimitri/pgextwlist.git Vcs-Browser: https://github.com/dimitri/pgextwlist Package: postgresql-PGVERSION-pgextwlist Section: libs Architecture: any Depends: postgresql-PGVERSION, ${misc:Depends}, ${shlibs:Depends}, Description: PostgreSQL Extension Whitelisting This extension implements extension whitelisting, and will actively prevent users from installing extensions not in the provided list. Also, this extension implements a form of sudo facility in that the whitelisted extensions will get installed as if superuser. Privileges are dropped before handing the control back to the user. pgextwlist-1.12/debian/copyright000066400000000000000000000022771414020221200170200ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: pgextwlist Source: https://github.com/dimitri/pgextwlist Files: * Copyright: 2011-2013 Dimitri Fontaine License: PostgreSQL Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. . IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. . THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pgextwlist-1.12/debian/pgversions000066400000000000000000000000051414020221200171720ustar00rootroot000000000000009.1+ pgextwlist-1.12/debian/rules000077500000000000000000000017561414020221200161460ustar00rootroot00000000000000#!/usr/bin/make -f override_dh_installdocs: dh_installdocs --all README.md # remove docs that belong elsewhere rm -rf debian/*/usr/share/doc/postgresql-doc-* override_dh_link: set -ex; for v in $(shell pg_buildext supported-versions); do mkdir debian/postgresql-$$v-pgextwlist/usr/lib/postgresql/$$v/lib/plugins; ln -s ../pgextwlist.so debian/postgresql-$$v-pgextwlist/usr/lib/postgresql/$$v/lib/plugins; done override_dh_pgxs_test: # work around extension_destdir bug with missing directory set -ex; for v in $(shell pg_buildext supported-versions); do mkdir -p debian/postgresql-$$v-pgextwlist/usr/share/postgresql/$$v/extension; done pg_buildext \ -o local_preload_libraries=pgextwlist \ -o extwlist.extensions=citext,earthdistance,pg_trgm,pg_stat_statements \ -o extwlist.custom_path=$(CURDIR)/test-scripts \ installcheck . . postgresql-%v-pgextwlist rmdir -p --ignore-fail-on-non-empty debian/postgresql-*-pgextwlist/usr/share/postgresql/*/extension %: dh $@ --with pgxs_loop pgextwlist-1.12/debian/source/000077500000000000000000000000001414020221200163555ustar00rootroot00000000000000pgextwlist-1.12/debian/source/format000066400000000000000000000000141414020221200175630ustar00rootroot000000000000003.0 (quilt) pgextwlist-1.12/debian/tests/000077500000000000000000000000001414020221200162175ustar00rootroot00000000000000pgextwlist-1.12/debian/tests/control000066400000000000000000000001331414020221200176170ustar00rootroot00000000000000Depends: postgresql-contrib-14, make, @, Tests: installcheck Restrictions: allow-stderr pgextwlist-1.12/debian/tests/control.in000066400000000000000000000001421414020221200202240ustar00rootroot00000000000000Depends: postgresql-contrib-PGVERSION, make, @, Tests: installcheck Restrictions: allow-stderr pgextwlist-1.12/debian/tests/installcheck000077500000000000000000000003171414020221200206120ustar00rootroot00000000000000#!/bin/sh set -e pg_buildext \ -o local_preload_libraries=pgextwlist \ -o extwlist.extensions=citext,earthdistance,pg_trgm,pg_stat_statements \ -o extwlist.custom_path=$PWD/test-scripts \ installcheck pgextwlist-1.12/debian/watch000066400000000000000000000001421414020221200161030ustar00rootroot00000000000000version=3 https://github.com/dimitri/pgextwlist/releases /dimitri/pgextwlist/archive/v(.*).tar.gz pgextwlist-1.12/expected/000077500000000000000000000000001414020221200154345ustar00rootroot00000000000000pgextwlist-1.12/expected/crossuser.out000066400000000000000000000006201414020221200202130ustar00rootroot00000000000000CREATE ROLE evil_user; SET ROLE mere_mortal; CREATE TABLE mere_table (t citext); SET ROLE evil_user; DROP EXTENSION citext; ERROR: cannot drop extension citext because other objects depend on it DETAIL: table mere_table column t depends on type citext HINT: Use DROP ... CASCADE to drop the dependent objects too. DROP EXTENSION citext CASCADE; NOTICE: drop cascades to table mere_table column t pgextwlist-1.12/expected/crossuser_1.out000066400000000000000000000006261414020221200204410ustar00rootroot00000000000000CREATE ROLE evil_user; SET ROLE mere_mortal; CREATE TABLE mere_table (t citext); SET ROLE evil_user; DROP EXTENSION citext; ERROR: cannot drop extension citext because other objects depend on it DETAIL: column t of table mere_table depends on type citext HINT: Use DROP ... CASCADE to drop the dependent objects too. DROP EXTENSION citext CASCADE; NOTICE: drop cascades to column t of table mere_table pgextwlist-1.12/expected/pgextwlist.out000066400000000000000000000033051414020221200204000ustar00rootroot00000000000000CREATE ROLE mere_mortal; SET ROLE mere_mortal; SHOW extwlist.extensions; extwlist.extensions ------------------------------------------------- citext,earthdistance,pg_trgm,pg_stat_statements (1 row) SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- pre-existing extension CREATE EXTENSION plpgsql; ERROR: extension "plpgsql" already exists SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- non-whitelisted extension CREATE EXTENSION hstore; ERROR: permission denied to create extension "hstore" HINT: Must be superuser to create this extension. SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- whitelisted extensions CREATE EXTENSION citext; CREATE EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext pg_trgm plpgsql (3 rows) -- whitelisted extension, but dependency is missing CREATE EXTENSION earthdistance; ERROR: required extension "cube" is not installed SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext pg_trgm plpgsql (3 rows) -- drop whitelisted extension DROP EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext plpgsql (2 rows) -- drop non-whitelisted extension DROP EXTENSION plpgsql; ERROR: must be owner of extension plpgsql SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext plpgsql (2 rows) -- whitelisted extension with custom after-create script CREATE EXTENSION pg_stat_statements; ERROR: syntax error at or near "'intentional error here'" SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext plpgsql (2 rows) pgextwlist-1.12/expected/pgextwlist_1.out000066400000000000000000000034211414020221200206170ustar00rootroot00000000000000CREATE ROLE mere_mortal; SET ROLE mere_mortal; SHOW extwlist.extensions; extwlist.extensions ------------------------------------------------- citext,earthdistance,pg_trgm,pg_stat_statements (1 row) SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- pre-existing extension CREATE EXTENSION plpgsql; ERROR: extension "plpgsql" already exists SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- non-whitelisted extension CREATE EXTENSION hstore; ERROR: permission denied to create extension "hstore" HINT: Must be superuser to create this extension. SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- whitelisted extensions CREATE EXTENSION citext; CREATE EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext pg_trgm plpgsql (3 rows) -- whitelisted extension, but dependency is missing CREATE EXTENSION earthdistance; ERROR: required extension "cube" is not installed HINT: Use CREATE EXTENSION ... CASCADE to install required extensions too. SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext pg_trgm plpgsql (3 rows) -- drop whitelisted extension DROP EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext plpgsql (2 rows) -- drop non-whitelisted extension DROP EXTENSION plpgsql; ERROR: must be owner of extension plpgsql SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext plpgsql (2 rows) -- whitelisted extension with custom after-create script CREATE EXTENSION pg_stat_statements; ERROR: syntax error at or near "'intentional error here'" SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext plpgsql (2 rows) pgextwlist-1.12/expected/pgextwlist_2.out000066400000000000000000000034561414020221200206300ustar00rootroot00000000000000CREATE ROLE mere_mortal; SET ROLE mere_mortal; SHOW extwlist.extensions; extwlist.extensions ------------------------------------------------- citext,earthdistance,pg_trgm,pg_stat_statements (1 row) SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- pre-existing extension CREATE EXTENSION plpgsql; ERROR: extension "plpgsql" already exists SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- non-whitelisted extension CREATE EXTENSION hstore; ERROR: permission denied to create extension "hstore" HINT: Must have CREATE privilege on current database to create this extension. SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- whitelisted extensions CREATE EXTENSION citext; CREATE EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext pg_trgm plpgsql (3 rows) -- whitelisted extension, but dependency is missing CREATE EXTENSION earthdistance; ERROR: required extension "cube" is not installed HINT: Use CREATE EXTENSION ... CASCADE to install required extensions too. SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext pg_trgm plpgsql (3 rows) -- drop whitelisted extension DROP EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext plpgsql (2 rows) -- drop non-whitelisted extension DROP EXTENSION plpgsql; ERROR: must be owner of extension plpgsql SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext plpgsql (2 rows) -- whitelisted extension with custom after-create script CREATE EXTENSION pg_stat_statements; ERROR: syntax error at or near "'intentional error here'" SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext plpgsql (2 rows) pgextwlist-1.12/pgextwlist.c000066400000000000000000000304661414020221200162220ustar00rootroot00000000000000/* PostgreSQL Extension WhiteList -- Dimitri Fontaine * * Author: Dimitri Fontaine * Licence: PostgreSQL * Copyright Dimitri Fontaine, 2011-2013 * * For a description of the features see the README.md file from the same * distribution. */ #include #include #include "postgres.h" #include "pgextwlist.h" #include "utils.h" #include "access/genam.h" #include "access/heapam.h" #include "access/xact.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/objectaccess.h" #include "catalog/pg_auth_members.h" #include "catalog/pg_authid.h" #include "catalog/pg_database.h" #include "catalog/pg_db_role_setting.h" #include "catalog/namespace.h" #include "commands/comment.h" #include "commands/dbcommands.h" #include "commands/seclabel.h" #include "commands/user.h" #if PG_MAJOR_VERSION >= 1000 #include "common/md5.h" #else #include "libpq/md5.h" #endif #include "funcapi.h" #include "miscadmin.h" #include "storage/lmgr.h" #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/timestamp.h" #if PG_MAJOR_VERSION < 1200 #include "utils/tqual.h" #endif #if PG_MAJOR_VERSION >= 1000 #include "utils/varlena.h" #endif /* #define DEBUG */ /* * This code has only been tested with PostgreSQL 9.1. * * It should be "deprecated" in 9.2 and following thanks to command triggers, * and the extension mechanism it works with didn't exist before 9.1. */ PG_MODULE_MAGIC; char *extwlist_extensions = NULL; char *extwlist_custom_path = NULL; static ProcessUtility_hook_type prev_ProcessUtility = NULL; void _PG_init(void); void _PG_fini(void); #if PG_MAJOR_VERSION < 903 #define PROCESS_UTILITY_PROTO_ARGS Node *parsetree, const char *queryString, \ ParamListInfo params, bool isTopLevel, \ DestReceiver *dest, char *completionTag #define PROCESS_UTILITY_ARGS parsetree, queryString, params, \ isTopLevel, dest, completionTag #elif PG_MAJOR_VERSION < 1000 #define PROCESS_UTILITY_PROTO_ARGS Node *parsetree, \ const char *queryString, \ ProcessUtilityContext context, \ ParamListInfo params, \ DestReceiver *dest, \ char *completionTag #define PROCESS_UTILITY_ARGS parsetree, queryString, context, \ params, dest, completionTag #elif PG_MAJOR_VERSION < 1300 #define PROCESS_UTILITY_PROTO_ARGS PlannedStmt *pstmt, \ const char *queryString, \ ProcessUtilityContext context, \ ParamListInfo params, \ QueryEnvironment *queryEnv, \ DestReceiver *dest, \ char *completionTag #define PROCESS_UTILITY_ARGS pstmt, queryString, context, \ params, queryEnv, dest, completionTag #elif PG_MAJOR_VERSION < 1400 #define PROCESS_UTILITY_PROTO_ARGS PlannedStmt *pstmt, \ const char *queryString, \ ProcessUtilityContext context, \ ParamListInfo params, \ QueryEnvironment *queryEnv, \ DestReceiver *dest, \ QueryCompletion *qc #define PROCESS_UTILITY_ARGS pstmt, queryString, context, \ params, queryEnv, dest, qc #else #define PROCESS_UTILITY_PROTO_ARGS PlannedStmt *pstmt, \ const char *queryString, \ bool readOnlyTree, \ ProcessUtilityContext context, \ ParamListInfo params, \ QueryEnvironment *queryEnv, \ DestReceiver *dest, \ QueryCompletion *qc #define PROCESS_UTILITY_ARGS pstmt, queryString, readOnlyTree, context, \ params, queryEnv, dest, qc #endif /* PG_MAJOR_VERSION */ #define EREPORT_EXTENSION_IS_NOT_WHITELISTED(op) \ ereport(ERROR, \ (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ errmsg("extension \"%s\" is not whitelisted", name), \ errdetail("%s the extension \"%s\" failed, " \ "because it is not on the whitelist of " \ "user-installable extensions.", op, name), \ errhint("Your system administrator has allowed users " \ "to install certain extensions. " \ "See: SHOW extwlist.extensions;"))); static void extwlist_ProcessUtility(PROCESS_UTILITY_PROTO_ARGS); static void call_ProcessUtility(PROCESS_UTILITY_PROTO_ARGS, const char *name, const char *schema, const char *old_version, const char *new_version, const char *action); static void call_RawProcessUtility(PROCESS_UTILITY_PROTO_ARGS); /* * _PG_init() - library load-time initialization * * DO NOT make this static nor change its name! * * Init the module, all we have to do here is getting our GUC */ void _PG_init(void) { PG_TRY(); { extwlist_extensions = GetConfigOptionByName("extwlist.extensions", NULL #if PG_MAJOR_VERSION >= 906 , false #endif ); } PG_CATCH(); { DefineCustomStringVariable("extwlist.extensions", "List of extensions that are whitelisted", "Separated by comma", &extwlist_extensions, "", PGC_SUSET, GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); EmitWarningsOnPlaceholders("extwlist.extensions"); } PG_END_TRY(); PG_TRY(); { extwlist_custom_path = GetConfigOptionByName("extwlist.custom_path", NULL #if PG_MAJOR_VERSION >= 906 , false #endif ); } PG_CATCH(); { DefineCustomStringVariable("extwlist.custom_path", "Directory where to load custom scripts from", "", &extwlist_custom_path, "", PGC_SUSET, GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); EmitWarningsOnPlaceholders("extwlist.custom_path"); } PG_END_TRY(); prev_ProcessUtility = ProcessUtility_hook; ProcessUtility_hook = extwlist_ProcessUtility; } /* * Module unload callback */ void _PG_fini(void) { /* Uninstall hook */ ProcessUtility_hook = prev_ProcessUtility; } /* * Extension Whitelisting includes mechanisms to run custom scripts before and * after the extension's provided script. * * We lookup scripts at the following places and run them when they exist: * * ${extwlist_custom_path}/${extname}/${when}--${version}.sql * ${extwlist_custom_path}/${extname}/${when}--${action}.sql * * - action is expected to be either "create" or "update" * - when is expected to be either "before" or "after" * * We don't validation the extension's name before building the scripts path * here because the extension name we are dealing with must have already been * added to the whitelist, which should be enough of a validation step. */ static void call_extension_scripts(const char *extname, const char *schema, const char *action, const char *when, const char *from_version, const char *version) { char *specific_custom_script = get_specific_custom_script_filename(extname, when, from_version, version); char *generic_custom_script = get_generic_custom_script_filename(extname, action, when); elog(DEBUG1, "Considering custom script \"%s\"", specific_custom_script); elog(DEBUG1, "Considering custom script \"%s\"", generic_custom_script); if (access(specific_custom_script, F_OK) == 0) execute_custom_script(specific_custom_script, schema); else if (access(generic_custom_script, F_OK) == 0) execute_custom_script(generic_custom_script, schema); } static bool extension_is_whitelisted(const char *name) { bool whitelisted = false; char *rawnames = pstrdup(extwlist_extensions); List *extensions; ListCell *lc; if (!SplitIdentifierString(rawnames, ',', &extensions)) { /* syntax error in extension name list */ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("parameter \"extwlist.extensions\" must be a list of extension names"))); } foreach(lc, extensions) { char *curext = (char *) lfirst(lc); if (!strcmp(name, curext)) { whitelisted = true; break; } } return whitelisted; } /* * ProcessUtility hook */ static void extwlist_ProcessUtility(PROCESS_UTILITY_PROTO_ARGS) { char *name = NULL; char *schema = NULL; char *old_version = NULL; char *new_version = NULL; #if PG_MAJOR_VERSION >= 1000 Node *parsetree = pstmt->utilityStmt; #endif /* Don't try to make life hard for our friendly superusers. */ if (superuser()) { call_RawProcessUtility(PROCESS_UTILITY_ARGS); return; } switch (nodeTag(parsetree)) { case T_CreateExtensionStmt: { CreateExtensionStmt *stmt = (CreateExtensionStmt *)parsetree; name = stmt->extname; fill_in_extension_properties(name, stmt->options, &schema, &old_version, &new_version); if (extension_is_whitelisted(name)) { call_ProcessUtility(PROCESS_UTILITY_ARGS, name, schema, old_version, new_version, "create"); return; } break; } case T_AlterExtensionStmt: { AlterExtensionStmt *stmt = (AlterExtensionStmt *)parsetree; name = stmt->extname; fill_in_extension_properties(name, stmt->options, &schema, &old_version, &new_version); /* fetch old_version from the catalogs, actually */ old_version = get_extension_current_version(name); if (extension_is_whitelisted(name)) { call_ProcessUtility(PROCESS_UTILITY_ARGS, name, schema, old_version, new_version, "update"); return; } break; } case T_DropStmt: if (((DropStmt *)parsetree)->removeType == OBJECT_EXTENSION) { /* DROP EXTENSION can target several of them at once */ bool all_in_whitelist = true; ListCell *lc; foreach(lc, ((DropStmt *)parsetree)->objects) { /* * For deconstructing the object list into actual names, * see the get_object_address_unqualified() function in * src/backend/catalog/objectaddress.c */ bool whitelisted = false; List *objname = lfirst(lc); #if PG_MAJOR_VERSION < 1000 name = strVal(linitial(objname)); #else name = strVal((Value *) objname); #endif whitelisted = extension_is_whitelisted(name); all_in_whitelist = all_in_whitelist && whitelisted; } /* * If we have a mix of whitelisted and non-whitelisted * extensions in a single DROP EXTENSION command, better play * safe and do the DROP without superpowers. * * So we only give superpowers when all extensions are in the * whitelist. */ if (all_in_whitelist) { call_ProcessUtility(PROCESS_UTILITY_ARGS, NULL, NULL, NULL, NULL, NULL); return; } } break; /* We intentionally don't support that command. */ case T_AlterExtensionContentsStmt: default: break; } /* * We can only fall here if we don't want to support the command, so pass * control over to the usual processing. */ call_RawProcessUtility(PROCESS_UTILITY_ARGS); } /* * Change current user and security context as if running a SECURITY DEFINER * procedure owned by a superuser, hard coded as the bootstrap user. */ static void call_ProcessUtility(PROCESS_UTILITY_PROTO_ARGS, const char *name, const char *schema, const char *old_version, const char *new_version, const char *action) { Oid save_userid; int save_sec_context; GetUserIdAndSecContext(&save_userid, &save_sec_context); SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID, save_sec_context | SECURITY_LOCAL_USERID_CHANGE | SECURITY_RESTRICTED_OPERATION); if (action) call_extension_scripts(name, schema, action, "before", old_version, new_version); call_RawProcessUtility(PROCESS_UTILITY_ARGS); if (action) call_extension_scripts(name, schema, action, "after", old_version, new_version); SetUserIdAndSecContext(save_userid, save_sec_context); } static void call_RawProcessUtility(PROCESS_UTILITY_PROTO_ARGS) { if (prev_ProcessUtility) prev_ProcessUtility(PROCESS_UTILITY_ARGS); else standard_ProcessUtility(PROCESS_UTILITY_ARGS); } pgextwlist-1.12/pgextwlist.h000066400000000000000000000007241414020221200162210ustar00rootroot00000000000000/* PostgreSQL Extension WhiteList -- Dimitri Fontaine * * Author: Dimitri Fontaine * Licence: PostgreSQL * Copyright Dimitri Fontaine, 2011-2013 * * For a description of the features see the README.md file from the same * distribution. */ #ifdef PG_VERSION_NUM #define PG_MAJOR_VERSION (PG_VERSION_NUM / 100) #else #error "Unknown PostgreSQL version" #endif #if PG_MAJOR_VERSION < 901 #error "Unsupported postgresql version" #endif pgextwlist-1.12/pgextwlist.spec000066400000000000000000000025721414020221200167270ustar00rootroot00000000000000Name: %{?package_prefix}pgextwlist Version: %{major_version} Release: %{minor_version}%{?dist} Summary: PostgreSQL Extension Whitelist extension Group: Applications/Databases License: PostgreSQL Requires: %{?package_prefix}%{!?package_prefix:postgresql-}server BuildRequires: %{?package_prefix}%{!?package_prefix:postgresql-}devel Source0: pgextwlist-rpm-src.tar.gz %description This extension implements extension whitelisting, and will actively prevent users from installing extensions not in the provided list. Also, this extension implements a form of sudo facility in that the whitelisted extensions will get installed as if superuser. Privileges are dropped before handing the control back to the user. %prep %setup -q -n pgextwlist %build sed '/^DOCS/d' -i Makefile # don't install README.md in pgsql/contrib make %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} %clean rm -rf %{buildroot} %files %defattr(-,root,root,-) %doc README.md %{?pkglibdir}%{!?pkglibdir:%{_libdir}/pgsql}/ %changelog * Sun Oct 15 2017 Christoph Berg - 1.6-0 - New upstream version. * Tue Sep 27 2016 Christoph Berg - 1.5-0 - New upstream version. * Tue Feb 02 2016 Christoph Berg - 1.4-0 - New upstream version. * Tue Jul 14 2015 Oskari Saarenmaa - 1.3-0 - Initial. pgextwlist-1.12/sql/000077500000000000000000000000001414020221200144325ustar00rootroot00000000000000pgextwlist-1.12/sql/crossuser.sql000066400000000000000000000002351414020221200172030ustar00rootroot00000000000000CREATE ROLE evil_user; SET ROLE mere_mortal; CREATE TABLE mere_table (t citext); SET ROLE evil_user; DROP EXTENSION citext; DROP EXTENSION citext CASCADE; pgextwlist-1.12/sql/pgextwlist.sql000066400000000000000000000016231414020221200173670ustar00rootroot00000000000000CREATE ROLE mere_mortal; SET ROLE mere_mortal; SHOW extwlist.extensions; SELECT extname FROM pg_extension ORDER BY 1; -- pre-existing extension CREATE EXTENSION plpgsql; SELECT extname FROM pg_extension ORDER BY 1; -- non-whitelisted extension CREATE EXTENSION hstore; SELECT extname FROM pg_extension ORDER BY 1; -- whitelisted extensions CREATE EXTENSION citext; CREATE EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; -- whitelisted extension, but dependency is missing CREATE EXTENSION earthdistance; SELECT extname FROM pg_extension ORDER BY 1; -- drop whitelisted extension DROP EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; -- drop non-whitelisted extension DROP EXTENSION plpgsql; SELECT extname FROM pg_extension ORDER BY 1; -- whitelisted extension with custom after-create script CREATE EXTENSION pg_stat_statements; SELECT extname FROM pg_extension ORDER BY 1; pgextwlist-1.12/test-scripts/000077500000000000000000000000001414020221200162775ustar00rootroot00000000000000pgextwlist-1.12/test-scripts/pg_stat_statements/000077500000000000000000000000001414020221200222075ustar00rootroot00000000000000pgextwlist-1.12/test-scripts/pg_stat_statements/after-create.sql000066400000000000000000000001511414020221200252670ustar00rootroot00000000000000GRANT EXECUTE ON FUNCTION @extschema@.pg_stat_statements_reset('intentional error here') TO mere_mortal; pgextwlist-1.12/utils.c000066400000000000000000000414721414020221200151470ustar00rootroot00000000000000/* PostgreSQL Extension WhiteList -- Dimitri Fontaine * * Author: Dimitri Fontaine * Licence: PostgreSQL * Copyright Dimitri Fontaine, 2011-2013 * * For a description of the features see the README.md file from the same * distribution. */ /* * Some tools to read a SQL file and execute commands found in there. * * The following code comes from the PostgreSQL source tree in * src/backend/commands/extension.c, with some modifications to run in the * context of the Extension Whitelisting Extension. */ #include #include #include #include "postgres.h" #include "pgextwlist.h" #include "utils.h" #if PG_MAJOR_VERSION >= 903 #include "access/htup_details.h" #else #include "access/htup.h" #endif #include "access/genam.h" #include "access/heapam.h" #include "access/skey.h" #include "access/xact.h" #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_collation.h" #include "catalog/pg_database.h" #include "catalog/pg_extension.h" #include "commands/extension.h" #include "executor/executor.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "storage/fd.h" #include "tcop/pquery.h" #include "tcop/utility.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/syscache.h" #if PG_MAJOR_VERSION < 1200 #include "utils/tqual.h" #define table_open(r, l) heap_open(r, l) #define table_close(r, l) heap_close(r, l) #endif /* * Parse contents of primary or auxiliary control file, and fill in * fields of *control. We parse primary file if version == NULL, * else the optional auxiliary file for that version. * * Control files are supposed to be very short, half a dozen lines, * so we don't worry about memory allocation risks here. Also we don't * worry about what encoding it's in; all values are expected to be ASCII. */ static void parse_default_version_in_control_file(const char *extname, char **version, char **schema) { char sharepath[MAXPGPATH]; char *filename; FILE *file; ConfigVariable *item, *head = NULL, *tail = NULL; /* * Locate the file to read. */ get_share_path(my_exec_path, sharepath); filename = (char *) palloc(MAXPGPATH); snprintf(filename, MAXPGPATH, "%s/extension/%s.control", sharepath, extname); if ((file = AllocateFile(filename, "r")) == NULL) { /* we still need to handle the following error here */ ereport(ERROR, (errcode_for_file_access(), errmsg("could not open extension control file \"%s\": %m", filename))); } /* * Parse the file content, using GUC's file parsing code. We need not * check the return value since any errors will be thrown at ERROR level. */ (void) ParseConfigFp(file, filename, 0, ERROR, &head, &tail); FreeFile(file); /* * Convert the ConfigVariable list into ExtensionControlFile entries, we * are only interested into the default version. */ for (item = head; item != NULL; item = item->next) { if (*version == NULL && strcmp(item->name, "default_version") == 0) { *version = pstrdup(item->value); } else if (*schema == NULL && strcmp(item->name, "schema") == 0) { *schema = pstrdup(item->value); } } FreeConfigVariables(head); pfree(filename); } /* * We lookup scripts at the following places and run them when they exist: * * ${extwlist_custom_path}/${extname}/${when}--${version}.sql * ${extwlist_custom_path}/${extname}/${when}-${action}.sql * * - action is expected to be either "create" or "update" * - when is expected to be either "before" or "after" */ char * get_generic_custom_script_filename(const char *name, const char *action, const char *when) { char *result; if (extwlist_custom_path == NULL) return NULL; result = (char *) palloc(MAXPGPATH); snprintf(result, MAXPGPATH, "%s/%s/%s-%s.sql", extwlist_custom_path, name, when, action); return result; } char * get_specific_custom_script_filename(const char *name, const char *when, const char *from_version, const char *version) { char *result; if (extwlist_custom_path == NULL) return NULL; result = (char *) palloc(MAXPGPATH); if (from_version) snprintf(result, MAXPGPATH, "%s/%s/%s--%s--%s.sql", extwlist_custom_path, name, when, from_version, version); else snprintf(result, MAXPGPATH, "%s/%s/%s--%s.sql", extwlist_custom_path, name, when, version); return result; } /* * At CREATE EXTENSION UPDATE time, we generally aren't provided with the * current version of the extension to upgrade, go fetch it from the catalogs. */ char * get_extension_current_version(const char *extname) { char *oldVersionName; Relation extRel; ScanKeyData key[1]; SysScanDesc extScan; HeapTuple extTup; Datum datum; bool isnull; /* * Look up the extension --- it must already exist in pg_extension */ extRel = table_open(ExtensionRelationId, AccessShareLock); ScanKeyInit(&key[0], Anum_pg_extension_extname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(extname)); extScan = systable_beginscan(extRel, ExtensionNameIndexId, true, SnapshotSelf, 1, key); extTup = systable_getnext(extScan); if (!HeapTupleIsValid(extTup)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("extension \"%s\" does not exist", extname))); /* * Determine the existing version we are updating from */ datum = heap_getattr(extTup, Anum_pg_extension_extversion, RelationGetDescr(extRel), &isnull); if (isnull) elog(ERROR, "extversion is null"); oldVersionName = text_to_cstring(DatumGetTextPP(datum)); systable_endscan(extScan); table_close(extRel, AccessShareLock); return oldVersionName; } /* * Read the statement's option list and set given parameters. */ void fill_in_extension_properties(const char *extname, List *options, char **schema, char **old_version, char **new_version) { ListCell *lc; DefElem *d_schema = NULL; DefElem *d_new_version = NULL; DefElem *d_old_version = NULL; /* * Read the statement option list, taking care not to issue any errors here * ourselves if at all possible: let the core code handle them. */ foreach(lc, options) { DefElem *defel = (DefElem *) lfirst(lc); if (strcmp(defel->defname, "schema") == 0) { d_schema = defel; } else if (strcmp(defel->defname, "new_version") == 0) { d_new_version = defel; } else if (strcmp(defel->defname, "old_version") == 0) { d_old_version = defel; } else { /* intentionnaly don't try and catch errors here */ } } if (d_schema && d_schema->arg) *schema = strVal(d_schema->arg); if (d_old_version && d_old_version->arg) *old_version = strVal(d_old_version->arg); if (d_new_version && d_new_version->arg) *new_version = strVal(d_new_version->arg); if (*new_version == NULL || *schema == NULL) /* fetch the default_version from the extension's control file */ parse_default_version_in_control_file(extname, new_version, schema); /* schema might be given neither in the statement nor the control file */ if (*schema == NULL) { /* * Use the current default creation namespace, which is the first * explicit entry in the search_path. */ Oid schemaOid; List *search_path = fetch_search_path(false); if (search_path == NIL) /* nothing valid in search_path? */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("no schema has been selected to create in"))); schemaOid = linitial_oid(search_path); *schema = get_namespace_name(schemaOid); if (*schema == NULL) /* recently-deleted namespace? */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("no schema has been selected to create in"))); list_free(search_path); } } /* * Read an SQL script file into a string, and convert to database encoding */ static char * read_custom_script_file(const char *filename) { int src_encoding, dest_encoding = GetDatabaseEncoding(); bytea *content; char *src_str; char *dest_str; int len; FILE *fp; struct stat fst; size_t nbytes; /* read_binary_file was made static in 9.5 so we'll reimplement the logic here */ if ((fp = AllocateFile(filename, PG_BINARY_R)) == NULL) ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" for reading: %m", filename))); if (fstat(fileno(fp), &fst) < 0) ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\" %m", filename))); content = (bytea *) palloc((Size) fst.st_size + VARHDRSZ); nbytes = fread(VARDATA(content), 1, (size_t) fst.st_size, fp); if (ferror(fp)) ereport(ERROR, (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", filename))); FreeFile(fp); SET_VARSIZE(content, nbytes + VARHDRSZ); /* use database encoding */ src_encoding = dest_encoding; /* make sure that source string is valid in the expected encoding */ len = VARSIZE_ANY_EXHDR(content); src_str = VARDATA_ANY(content); pg_verify_mbstr_len(src_encoding, src_str, len, false); /* convert the encoding to the database encoding */ dest_str = (char *) pg_do_encoding_conversion((unsigned char *) src_str, len, src_encoding, dest_encoding); /* if no conversion happened, we have to arrange for null termination */ if (dest_str == src_str) { dest_str = (char *) palloc(len + 1); memcpy(dest_str, src_str, len); dest_str[len] = '\0'; } return dest_str; } /* * Execute given SQL string. * * filename is used only to report errors. * * Note: it's tempting to just use SPI to execute the string, but that does * not work very well. The really serious problem is that SPI will parse, * analyze, and plan the whole string before executing any of it; of course * this fails if there are any plannable statements referring to objects * created earlier in the script. A lesser annoyance is that SPI insists * on printing the whole string as errcontext in case of any error, and that * could be very long. */ static void execute_sql_string(const char *sql, const char *filename) { List *raw_parsetree_list; DestReceiver *dest; ListCell *lc1; /* * Parse the SQL string into a list of raw parse trees. */ raw_parsetree_list = pg_parse_query(sql); /* All output from SELECTs goes to the bit bucket */ dest = CreateDestReceiver(DestNone); /* * Do parse analysis, rule rewrite, planning, and execution for each raw * parsetree. We must fully execute each query before beginning parse * analysis on the next one, since there may be interdependencies. */ foreach(lc1, raw_parsetree_list) { #if PG_MAJOR_VERSION >= 1000 RawStmt *parsetree = lfirst_node(RawStmt, lc1); #else Node *parsetree = (Node *) lfirst(lc1); #endif List *stmt_list; ListCell *lc2; stmt_list = pg_analyze_and_rewrite(parsetree, sql, NULL, 0 #if PG_MAJOR_VERSION >= 1000 , NULL #endif ); stmt_list = pg_plan_queries(stmt_list, #if PG_MAJOR_VERSION >= 1300 sql, #endif 0, NULL); foreach(lc2, stmt_list) { #if PG_MAJOR_VERSION >= 1000 PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2); #else Node *stmt = (Node *) lfirst(lc2); #endif if (IsA(stmt, TransactionStmt)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("transaction control statements are not allowed within an extension script"))); CommandCounterIncrement(); PushActiveSnapshot(GetTransactionSnapshot()); if (IsA(stmt, PlannedStmt) && ((PlannedStmt *) stmt)->utilityStmt == NULL) { QueryDesc *qdesc; qdesc = CreateQueryDesc((PlannedStmt *) stmt, sql, GetActiveSnapshot(), NULL, dest, NULL, #if PG_MAJOR_VERSION >= 1000 NULL, #endif 0); ExecutorStart(qdesc, 0); ExecutorRun(qdesc, ForwardScanDirection, 0 #if PG_MAJOR_VERSION >= 1000 , true #endif ); ExecutorFinish(qdesc); ExecutorEnd(qdesc); FreeQueryDesc(qdesc); } else { ProcessUtility(stmt, sql, #if PG_MAJOR_VERSION >= 1400 false, /* no need to copy */ #endif #if PG_MAJOR_VERSION >= 903 PROCESS_UTILITY_QUERY, #endif NULL, #if PG_MAJOR_VERSION >= 1000 NULL, #endif #if PG_MAJOR_VERSION < 903 false, /* not top level */ #endif dest, NULL); } PopActiveSnapshot(); } } /* Be sure to advance the command counter after the last script command */ CommandCounterIncrement(); } /* * get_current_database_owner_name * * select rolname * from pg_roles u join pg_database d on d.datdba = u.oid * where datname = current_database(); */ static char * get_current_database_owner_name() { HeapTuple dbtuple; Oid owner; dbtuple = SearchSysCache1(DATABASEOID, MyDatabaseId); if (HeapTupleIsValid(dbtuple)) { owner = ((Form_pg_database) GETSTRUCT(dbtuple))->datdba; ReleaseSysCache(dbtuple); } else return NULL; return GetUserNameFromId(owner #if PG_MAJOR_VERSION >= 905 , false #endif ); } /* * Execute given script */ void execute_custom_script(const char *filename, const char *schemaName) { int save_nestlevel; StringInfoData pathbuf; const char *qSchemaName = quote_identifier(schemaName); elog(DEBUG1, "Executing custom script \"%s\"", filename); /* * Force client_min_messages and log_min_messages to be at least WARNING, * so that we won't spam the user with useless NOTICE messages from common * script actions like creating shell types. * * We use the equivalent of a function SET option to allow the setting to * persist for exactly the duration of the script execution. guc.c also * takes care of undoing the setting on error. */ save_nestlevel = NewGUCNestLevel(); if (client_min_messages < WARNING) (void) set_config_option("client_min_messages", "warning", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true #if PG_MAJOR_VERSION >= 902 , 0 #endif #if PG_MAJOR_VERSION >= 905 , false #endif ); if (log_min_messages < WARNING) (void) set_config_option("log_min_messages", "warning", PGC_SUSET, PGC_S_SESSION, GUC_ACTION_SAVE, true #if PG_MAJOR_VERSION >= 902 , 0 #endif #if PG_MAJOR_VERSION >= 905 , false #endif ); /* * Set up the search path to contain the target schema, then the schemas * of any prerequisite extensions, and nothing else. In particular this * makes the target schema be the default creation target namespace. * * Note: it might look tempting to use PushOverrideSearchPath for this, * but we cannot do that. We have to actually set the search_path GUC in * case the extension script examines or changes it. In any case, the * GUC_ACTION_SAVE method is just as convenient. */ initStringInfo(&pathbuf); appendStringInfoString(&pathbuf, quote_identifier(schemaName)); (void) set_config_option("search_path", pathbuf.data, PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true #if PG_MAJOR_VERSION >= 902 , 0 #endif #if PG_MAJOR_VERSION >= 905 , false #endif ); PG_TRY(); { char *c_sql = read_custom_script_file(filename); Datum t_sql; /* We use various functions that want to operate on text datums */ t_sql = CStringGetTextDatum(c_sql); /* * Reduce any lines beginning with "\echo" to empty. This allows * scripts to contain messages telling people not to run them via * psql, which has been found to be necessary due to old habits. */ t_sql = DirectFunctionCall4Coll(textregexreplace, C_COLLATION_OID, t_sql, CStringGetTextDatum("^\\\\echo.*$"), CStringGetTextDatum(""), CStringGetTextDatum("ng")); /* * substitute the target schema name for occurrences of @extschema@. */ t_sql = DirectFunctionCall3Coll(replace_text, C_COLLATION_OID, t_sql, CStringGetTextDatum("@extschema@"), CStringGetTextDatum(qSchemaName)); /* * substitute the current user name for occurrences of @current_user@ */ t_sql = DirectFunctionCall3Coll(replace_text, C_COLLATION_OID, t_sql, CStringGetTextDatum("@current_user@"), CStringGetTextDatum( GetUserNameFromId(GetUserId() #if PG_MAJOR_VERSION >= 905 , false #endif ))); /* * substitute the database owner for occurrences of @database_owner@ */ t_sql = DirectFunctionCall3Coll(replace_text, C_COLLATION_OID, t_sql, CStringGetTextDatum("@database_owner@"), CStringGetTextDatum( get_current_database_owner_name())); /* And now back to C string */ c_sql = text_to_cstring(DatumGetTextPP(t_sql)); execute_sql_string(c_sql, filename); } PG_CATCH(); { PG_RE_THROW(); } PG_END_TRY(); /* * Restore the GUC variables we set above. */ AtEOXact_GUC(true, save_nestlevel); } pgextwlist-1.12/utils.h000066400000000000000000000020401414020221200151400ustar00rootroot00000000000000/* PostgreSQL Extension WhiteList -- Dimitri Fontaine * * Author: Dimitri Fontaine * Licence: PostgreSQL * Copyright Dimitri Fontaine, 2011-2013 * * For a description of the features see the README.md file from the same * distribution. */ #ifndef __UTILS_H__ #define __UTILS_H__ #include "utils/builtins.h" #include "nodes/pg_list.h" #define MAXPGPATH 1024 extern char *extwlist_extensions; extern char *extwlist_custom_path; char *get_specific_custom_script_filename(const char *name, const char *when, const char *from_version, const char *version); char *get_generic_custom_script_filename(const char *name, const char *action, const char *when); char *get_extension_current_version(const char *extname); void fill_in_extension_properties(const char *extname, List *options, char **schema, char **old_version, char **new_version); void execute_custom_script(const char *schemaName, const char *filename); #endif