Thread: Removing pg_pltemplate and creating "trustable" extensions

Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
We've repeatedly kicked around the idea of getting rid of the
pg_pltemplate catalog in favor of keeping that information directly in
the languages' extension files [1][2][3][4].  The primary abstract
argument for that is that it removes a way in which our in-tree PLs
are special compared to out-of-tree PLs, which can't have entries in
pg_pltemplate.  A concrete argument for it is that it might simplify
fixing the python-2-vs-python-3 mess, since one of the issues there
is that pg_pltemplate has hard-wired knowledge that "plpythonu" is
Python 2.  Accordingly, attached is a patch series that ends by
removing that catalog.

As I noted in [2], the main stumbling block to doing this is that
the code associated with pg_pltemplate provides a privilege override
mechanism that allows non-superuser database owners to install trusted
PLs.  For backwards compatibility if nothing else, we probably want to
keep that ability, though it'd be nice if it weren't such a hard-wired
behavior.

Patch 0001 below addresses this problem by inventing a concept of
"trustable" (not necessarily trusted) extensions.  An extension that
would normally require superuser permissions (e.g., because it creates
C functions) can now be installed by a non-superuser if (a) it is
marked trustable in the extension's control file, AND (b) it is
listed as trusted in one of two new GUCs, trusted_extensions_dba and
trusted_extensions_anyone.  (These names could stand a visit to the
bikeshed, no doubt.)  Extensions matching trusted_extensions_dba can
be installed by a database owner, while extensions matching
trusted_extensions_anyone can be installed by anybody.  The default
settings of these GUCs provide backwards-compatible behavior, but
they can be adjusted to provide more or less ability to install
extensions.  (This design is basically what Andres advocated in [2].)

In this patch series, I've only marked the trusted-PL extensions as
trustable, but we should probably make most of the contrib extensions
trustable --- not, say, adminpack, but surely most of the datatype
and transform modules could be marked trustable.  (Maybe we could
make the default GUC settings more permissive, too.)

As coded, the two GUCs are not lists of extension names but rather
regexes.  You could use them as lists, eg "^plperl$|^plpgsql$|^pltcl$"
but that's a bit tedious, especially if someone wants to trust most
or all of contrib.  I am a tad worried about user-friendliness of
this notation, but I think we need something with wild-cards, and
that's the only wild-card-capable matching engine we have available
at a low level.

You might wonder why bother with the trustable flag rather than just
relying on the GUCs.  The answer is mostly paranoia: I'm worried about
somebody writing e.g. "plperl" with no anchors and not realizing that
that will match "plperlu" as well.  Anyway, since we're talking about
potential escalation-to-superuser security problems, I think having
both belt and suspenders protection on untrusted languages is wise.

There are no regression tests for this functionality in 0001,
but I added one in 0002.

Patch 0002 converts all the in-tree PLs to use fully specified
CREATE LANGUAGE and not rely on pg_pltemplate.

I had a better idea about how to manage permissions than what was
discussed in [3]; we can just give ownership of the language
object to the user calling CREATE EXTENSION.  Doing it that way
means that we end up with exactly the same catalog state as we
do in existing releases.  And that should mean that we don't have
to treat this as an extension version upgrade.  So I just modified
the 1.0 scripts in-place instead of adding 1.0--1.1 scripts.  It
looks to me like there's no need to touch the from-unpackaged
scripts, either.  And by the same token this isn't really an issue
for pg_upgrade.

(I noticed while testing this that pg_upgrade fails to preserve
ownership on extensions, but that's not new; this patch is not
making that situation any better or worse than it was.  Still,
maybe we oughta try to fix that sometime soon too.)

Patch 0003 removes CREATE LANGUAGE's reliance on pg_pltemplate.
CREATE LANGUAGE without parameters is now interpreted as
CREATE EXTENSION, thus providing a forward compatibility path
for old dump files.

Note: this won't help for *really* old dump files, ie those containing
CREATE LANGUAGE commands that do have parameters but the parameters are
wrong according to modern usage.  This is a hazard for dumps coming
from 8.0 or older servers; we invented pg_pltemplate in 8.1 primarily
as a way of cleaning up such dumps [5].  I think that that's far enough
back that we don't have to worry about how convenient it will be to go
from 8.0-or-older to v13-or-newer in one jump.

Finally, patch 0004 removes the now-unused catalog and cleans up some
incidental comments referring to it.

Once this is in, we could start thinking about whether we actually
want to change anything about plpython in the near future.

            regards, tom lane

[1] https://www.postgresql.org/message-id/flat/763f2fe4-743f-d530-8831-20811edd3d6a%402ndquadrant.com
[2] https://www.postgresql.org/message-id/flat/7495.1524861244%40sss.pgh.pa.us
[3] https://www.postgresql.org/message-id/flat/5351890.TdMePpdHBD%40nb.usersys.redhat.com
[4] https://www.postgresql.org/message-id/flat/CAKmB1PGDAy9mXxSTqUchYEi4iJAA6NKVj4P5BtAzvQ9wSDUwJw@mail.gmail.com
[5] https://www.postgresql.org/message-id/flat/5088.1125525412@sss.pgh.pa.us

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 5e71a2e..4c32fd8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8520,7 +8520,15 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
      <row>
       <entry><structfield>superuser</structfield></entry>
       <entry><type>bool</type></entry>
-      <entry>True if only superusers are allowed to install this extension</entry>
+      <entry>True if only superusers are allowed to install this extension
+       (but see <structfield>trustable</structfield>)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>trustable</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry>True if the extension can be installed by non-superusers when
+       configuration settings permit</entry>
      </row>

      <row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 89284dc..ff4520b 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8447,6 +8447,78 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>

+     <varlistentry id="guc-trusted-extensions-dba" xreflabel="trusted_extensions_dba">
+      <term><varname>trusted_extensions_dba</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>trusted_extensions_dba</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        This parameter is a regular expression that matches the names of
+        trustable extensions that database owners are allowed to install
+        into their databases
+        (that is, run <xref linkend="sql-createextension"/> on; the
+        extension's files must already be present in the installation).
+        See <xref linkend="extend-extensions"/> for more information.
+        Regular expressions are explained in
+        <xref linkend="posix-syntax-details"/>.
+       </para>
+
+       <para>
+        The default value for this parameter is
+        <literal>'^pl'</literal>, which matches any extension whose name
+        begins with <literal>pl</literal>.  This allows database owners to
+        install trusted procedural languages, which was the hard-wired
+        behavior in older <productname>PostgreSQL</productname> versions.
+       </para>
+
+       <para>
+        This parameter can be changed at run time by superusers, but a
+        setting done that way will only persist until the end of the
+        client connection, so this method should be reserved for
+        development purposes. The recommended way to set this parameter
+        is in the <filename>postgresql.conf</filename> configuration
+        file.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="guc-trusted-extensions-anyone" xreflabel="trusted_extensions_anyone">
+      <term><varname>trusted_extensions_anyone</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>trusted_extensions_anyone</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        This parameter is a regular expression that matches the names of
+        trustable extensions that any SQL user is allowed to install
+        (that is, run <xref linkend="sql-createextension"/> on; the
+        extension's files must already be present in the installation).
+        See <xref linkend="extend-extensions"/> for more information.
+        Regular expressions are explained in
+        <xref linkend="posix-syntax-details"/>.
+       </para>
+
+       <para>
+        The default value for this parameter is
+        <literal>'^$'</literal>, which matches no extension names, thus
+        forbidding unprivileged users from installing any extensions that
+        require superuser privileges to install.
+       </para>
+
+       <para>
+        This parameter can be changed at run time by superusers, but a
+        setting done that way will only persist until the end of the
+        client connection, so this method should be reserved for
+        development purposes. The recommended way to set this parameter
+        is in the <filename>postgresql.conf</filename> configuration
+        file.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-gin-fuzzy-search-limit" xreflabel="gin_fuzzy_search_limit">
       <term><varname>gin_fuzzy_search_limit</varname> (<type>integer</type>)
       <indexterm>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index b5e59d5..8049934 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -576,6 +576,33 @@
         version.  If it is set to <literal>false</literal>, just the privileges
         required to execute the commands in the installation or update script
         are required.
+        This should normally be set to <literal>true</literal> if any of the
+        script commands require superuser privileges.  (Such commands would
+        fail anyway, but it's more user-friendly to give the error up front.)
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>trustable</varname> (<type>boolean</type>)</term>
+      <listitem>
+       <para>
+        This parameter, if set to <literal>true</literal> (which is not the
+        default), allows non-superusers to install an extension that
+        has <varname>superuser</varname> set to <literal>true</literal>.
+        The extension must <emphasis>also</emphasis> be considered
+        trustable by the installation's configuration parameters (see
+        <xref linkend="guc-trusted-extensions-dba"/> and
+        <xref linkend="guc-trusted-extensions-anyone"/>) before this is
+        allowed.  If both requirements hold and the user
+        executing <command>CREATE EXTENSION</command> is not a superuser,
+        then the installation or update script is run as the bootstrap
+        superuser, not as the calling user.
+        This parameter is irrelevant if <varname>superuser</varname> is
+        <literal>false</literal>.
+        Generally, this should not be set true for extensions that would
+        allow access to otherwise-superuser-only abilities, such as
+        untrusted procedural languages.
        </para>
       </listitem>
      </varlistentry>
@@ -642,6 +669,18 @@
     </para>

     <para>
+     If the extension script contains the
+     string <literal>@extowner@</literal>, that string is replaced with the
+     (suitably quoted) name of the user calling <command>CREATE
+     EXTENSION</command> or <command>ALTER EXTENSION</command>.  Typically
+     this feature is used by extensions that are marked trustable to assign
+     ownership of selected objects to the calling user rather than the
+     bootstrap superuser.  (One should be careful about doing so, however.
+     For example, assigning ownership of a C-language function to a
+     non-superuser would create a privilege escalation path for that user.)
+    </para>
+
+    <para>
      While the script files can contain any characters allowed by the specified
      encoding, control files should contain only plain ASCII, because there
      is no way for <productname>PostgreSQL</productname> to know what encoding a
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index 36837f9..ee59bfe 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -47,14 +47,28 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
   </para>

   <para>
-   Loading an extension requires the same privileges that would be
-   required to create its component objects.  For most extensions this
-   means superuser or database owner privileges are needed.
+   Loading an extension ordinarily requires the same privileges that would be
+   required to create its component objects.  For many extensions this
+   means superuser privileges are needed.
    The user who runs <command>CREATE EXTENSION</command> becomes the
    owner of the extension for purposes of later privilege checks, as well
    as the owner of any objects created by the extension's script.
   </para>

+  <para>
+   However, if the extension is marked <firstterm>trustable</firstterm> in
+   its control file, and it is trusted by the local installation according
+   to the relevant configuration parameter
+   (<xref linkend="guc-trusted-extensions-dba"/> or
+   <xref linkend="guc-trusted-extensions-anyone"/>),
+   then it can be installed by a non-superuser.  In this case the extension
+   object itself will be owned by the calling user, but the contained
+   objects will be owned by the bootstrap superuser (unless the
+   extension's script explicitly assigns them to the calling user).
+   This configuration gives the calling user the right to drop the
+   extension, but not to modify individual objects within it.
+  </para>
+
  </refsect1>

  <refsect1>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e..f3e4d43 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -317,7 +317,8 @@ CREATE VIEW pg_available_extensions AS

 CREATE VIEW pg_available_extension_versions AS
     SELECT E.name, E.version, (X.extname IS NOT NULL) AS installed,
-           E.superuser, E.relocatable, E.schema, E.requires, E.comment
+           E.superuser, E.trustable, E.relocatable,
+           E.schema, E.requires, E.comment
       FROM pg_available_extension_versions() AS E
            LEFT JOIN pg_extension AS X
              ON E.name = X.extname AND E.version = X.extversion;
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index f7202cc..257b4fc 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -40,6 +40,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
@@ -54,6 +55,7 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "regex/regex.h"
 #include "storage/fd.h"
 #include "tcop/utility.h"
 #include "utils/acl.h"
@@ -66,6 +68,10 @@
 #include "utils/varlena.h"


+/* GUC settings */
+char       *trusted_extensions_dba;
+char       *trusted_extensions_anyone;
+
 /* Globally visible state variables */
 bool        creating_extension = false;
 Oid            CurrentExtensionObject = InvalidOid;
@@ -84,6 +90,7 @@ typedef struct ExtensionControlFile
     char       *schema;            /* target schema (allowed if !relocatable) */
     bool        relocatable;    /* is ALTER EXTENSION SET SCHEMA supported? */
     bool        superuser;        /* must be superuser to install? */
+    bool        trustable;        /* allow becoming superuser on the fly? */
     int            encoding;        /* encoding of the script file, or -1 */
     List       *requires;        /* names of prerequisite extensions */
 } ExtensionControlFile;
@@ -558,6 +565,14 @@ parse_extension_control_file(ExtensionControlFile *control,
                          errmsg("parameter \"%s\" requires a Boolean value",
                                 item->name)));
         }
+        else if (strcmp(item->name, "trustable") == 0)
+        {
+            if (!parse_bool(item->value, &control->trustable))
+                ereport(ERROR,
+                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                         errmsg("parameter \"%s\" requires a Boolean value",
+                                item->name)));
+        }
         else if (strcmp(item->name, "encoding") == 0)
         {
             control->encoding = pg_valid_server_encoding(item->value);
@@ -614,6 +629,7 @@ read_extension_control_file(const char *extname)
     control->name = pstrdup(extname);
     control->relocatable = false;
     control->superuser = true;
+    control->trustable = false;
     control->encoding = -1;

     /*
@@ -795,6 +811,76 @@ execute_sql_string(const char *sql)
 }

 /*
+ * Check if "str" matches the regular expression "pattern".
+ *
+ * XXX Perhaps we should put this somewhere else?
+ */
+static bool
+string_matches_regex(const char *str, const char *pattern)
+{
+    int            r;
+    pg_wchar   *wstr;
+    int            wlen;
+    regex_t        re;
+    char        errstr[100];
+
+    /* The regex library wants to deal in wchars not chars */
+    wstr = palloc((strlen(pattern) + 1) * sizeof(pg_wchar));
+    wlen = pg_mb2wchar_with_len(pattern, wstr, strlen(pattern));
+
+    r = pg_regcomp(&re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
+    if (r)
+    {
+        /* This shouldn't really happen, since guc.c checked the value */
+        pg_regerror(r, &re, errstr, sizeof(errstr));
+        /* no need for pg_regfree here */
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+                 errmsg("invalid regular expression \"%s\": %s",
+                        pattern, errstr)));
+    }
+    pfree(wstr);
+
+    wstr = palloc((strlen(str) + 1) * sizeof(pg_wchar));
+    wlen = pg_mb2wchar_with_len(str, wstr, strlen(str));
+
+    r = pg_regexec(&re, wstr, wlen, 0, NULL, 0, NULL, 0);
+    if (r != REG_OKAY && r != REG_NOMATCH)
+    {
+        pg_regerror(r, &re, errstr, sizeof(errstr));
+        pg_regfree(&re);
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+                 errmsg("regular expression match for \"%s\" failed: %s",
+                        pattern, errstr)));
+    }
+    pfree(wstr);
+
+    pg_regfree(&re);
+    return (r == REG_OKAY);
+}
+
+/*
+ * Policy function: is the given extension trusted for installation by a
+ * non-superuser?
+ */
+static bool
+extension_is_trusted(ExtensionControlFile *control)
+{
+    /* Never trust unless extension's control file says it's okay */
+    if (!control->trustable)
+        return false;
+    /* Database owner can install, if it matches appropriate GUC */
+    if (pg_database_ownercheck(MyDatabaseId, GetUserId()) &&
+        string_matches_regex(control->name, trusted_extensions_dba))
+        return true;
+    /* Anyone can install, if it matches that GUC */
+    if (string_matches_regex(control->name, trusted_extensions_anyone))
+        return true;
+    return false;
+}
+
+/*
  * Execute the appropriate script file for installing or updating the extension
  *
  * If from_version isn't NULL, it's an update
@@ -806,19 +892,24 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                          List *requiredSchemas,
                          const char *schemaName, Oid schemaOid)
 {
+    bool        switch_to_superuser = false;
     char       *filename;
+    Oid            save_userid = 0;
+    int            save_sec_context = 0;
     int            save_nestlevel;
     StringInfoData pathbuf;
     ListCell   *lc;

     /*
-     * Enforce superuser-ness if appropriate.  We postpone this check until
-     * here so that the flag is correctly associated with the right script(s)
-     * if it's set in secondary control files.
+     * Enforce superuser-ness if appropriate.  We postpone these checks until
+     * here so that the control flags are correctly associated with the right
+     * script(s) if they happen to be set in secondary control files.
      */
     if (control->superuser && !superuser())
     {
-        if (from_version == NULL)
+        if (extension_is_trusted(control))
+            switch_to_superuser = true;
+        else if (from_version == NULL)
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("permission denied to create extension \"%s\"",
@@ -835,6 +926,18 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
     filename = get_extension_script_filename(control, from_version, version);

     /*
+     * If installing a trusted extension on behalf of a non-superuser, become
+     * the bootstrap superuser.  (This switch will be cleaned up automatically
+     * if the transaction aborts, as will the GUC changes below.)
+     */
+    if (switch_to_superuser)
+    {
+        GetUserIdAndSecContext(&save_userid, &save_sec_context);
+        SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
+                               save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+    }
+
+    /*
      * 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.
@@ -907,6 +1010,22 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                                         CStringGetTextDatum("ng"));

         /*
+         * If the script uses @extowner@, substitute the calling username.
+         */
+        if (strstr(c_sql, "@extowner@"))
+        {
+            Oid            uid = switch_to_superuser ? save_userid : GetUserId();
+            const char *userName = GetUserNameFromId(uid, false);
+            const char *qUserName = quote_identifier(userName);
+
+            t_sql = DirectFunctionCall3Coll(replace_text,
+                                            C_COLLATION_OID,
+                                            t_sql,
+                                            CStringGetTextDatum("@extowner@"),
+                                            CStringGetTextDatum(qUserName));
+        }
+
+        /*
          * If it's not relocatable, substitute the target schema name for
          * occurrences of @extschema@.
          *
@@ -957,6 +1076,12 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
      * Restore the GUC variables we set above.
      */
     AtEOXact_GUC(true, save_nestlevel);
+
+    /*
+     * Restore authentication state if needed.
+     */
+    if (switch_to_superuser)
+        SetUserIdAndSecContext(save_userid, save_sec_context);
 }

 /*
@@ -2117,8 +2242,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
     {
         ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
         ExtensionControlFile *control;
-        Datum        values[7];
-        bool        nulls[7];
+        Datum        values[8];
+        bool        nulls[8];
         ListCell   *lc2;

         if (!evi->installable)
@@ -2139,24 +2264,26 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
         values[1] = CStringGetTextDatum(evi->name);
         /* superuser */
         values[2] = BoolGetDatum(control->superuser);
+        /* trustable */
+        values[3] = BoolGetDatum(control->trustable);
         /* relocatable */
-        values[3] = BoolGetDatum(control->relocatable);
+        values[4] = BoolGetDatum(control->relocatable);
         /* schema */
         if (control->schema == NULL)
-            nulls[4] = true;
+            nulls[5] = true;
         else
-            values[4] = DirectFunctionCall1(namein,
+            values[5] = DirectFunctionCall1(namein,
                                             CStringGetDatum(control->schema));
         /* requires */
         if (control->requires == NIL)
-            nulls[5] = true;
+            nulls[6] = true;
         else
-            values[5] = convert_requires_to_datum(control->requires);
+            values[6] = convert_requires_to_datum(control->requires);
         /* comment */
         if (control->comment == NULL)
-            nulls[6] = true;
+            nulls[7] = true;
         else
-            values[6] = CStringGetTextDatum(control->comment);
+            values[7] = CStringGetTextDatum(control->comment);

         tuplestore_putvalues(tupstore, tupdesc, values, nulls);

@@ -2184,16 +2311,18 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
                 values[1] = CStringGetTextDatum(evi2->name);
                 /* superuser */
                 values[2] = BoolGetDatum(control->superuser);
+                /* trustable */
+                values[3] = BoolGetDatum(control->trustable);
                 /* relocatable */
-                values[3] = BoolGetDatum(control->relocatable);
+                values[4] = BoolGetDatum(control->relocatable);
                 /* schema stays the same */
                 /* requires */
                 if (control->requires == NIL)
-                    nulls[5] = true;
+                    nulls[6] = true;
                 else
                 {
-                    values[5] = convert_requires_to_datum(control->requires);
-                    nulls[5] = false;
+                    values[6] = convert_requires_to_datum(control->requires);
+                    nulls[6] = false;
                 }
                 /* comment stays the same */

diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 90ffd89..614d993 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -36,7 +36,9 @@
 #include "access/xlog_internal.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_collation.h"
 #include "commands/async.h"
+#include "commands/extension.h"
 #include "commands/prepare.h"
 #include "commands/user.h"
 #include "commands/vacuum.h"
@@ -65,6 +67,7 @@
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "postmaster/walwriter.h"
+#include "regex/regex.h"
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
@@ -175,6 +178,7 @@ static bool check_ssl(bool *newval, void **extra, GucSource source);
 static bool check_stage_log_stats(bool *newval, void **extra, GucSource source);
 static bool check_log_stats(bool *newval, void **extra, GucSource source);
 static bool check_canonical_path(char **newval, void **extra, GucSource source);
+static bool check_regular_expression(char **newval, void **extra, GucSource source);
 static bool check_timezone_abbreviations(char **newval, void **extra, GucSource source);
 static void assign_timezone_abbreviations(const char *newval, void *extra);
 static void pg_timezone_abbrev_initialize(void);
@@ -4178,6 +4182,26 @@ static struct config_string ConfigureNamesString[] =
     },

     {
+        {"trusted_extensions_dba", PGC_SUSET, CLIENT_CONN_OTHER,
+            gettext_noop("Selects which trustable extensions may be installed by database owners."),
+            NULL
+        },
+        &trusted_extensions_dba,
+        "^pl",
+        check_regular_expression, NULL, NULL
+    },
+
+    {
+        {"trusted_extensions_anyone", PGC_SUSET, CLIENT_CONN_OTHER,
+            gettext_noop("Selects which trustable extensions may be installed by anyone."),
+            NULL
+        },
+        &trusted_extensions_anyone,
+        "^$",
+        check_regular_expression, NULL, NULL
+    },
+
+    {
         {"wal_consistency_checking", PGC_SUSET, DEVELOPER_OPTIONS,
             gettext_noop("Sets the WAL resource managers for which WAL consistency checks are done."),
             gettext_noop("Full-page images will be logged for all data blocks and cross-checked against the results of
WALreplay."), 
@@ -11114,6 +11138,37 @@ check_canonical_path(char **newval, void **extra, GucSource source)
     return true;
 }

+/* Check that the GUC's value is a valid regular expression. */
+static bool
+check_regular_expression(char **newval, void **extra, GucSource source)
+{
+    int            r;
+    pg_wchar   *wstr;
+    int            wlen;
+    regex_t        re;
+
+    if (!*newval)
+        return false;
+
+    /* The regex library wants to deal in wchars not chars */
+    wstr = palloc((strlen(*newval) + 1) * sizeof(pg_wchar));
+    wlen = pg_mb2wchar_with_len(*newval, wstr, strlen(*newval));
+
+    r = pg_regcomp(&re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
+    if (r)
+    {
+        char        errstr[100];
+
+        pg_regerror(r, &re, errstr, sizeof(errstr));
+        GUC_check_errdetail("invalid regular expression: %s", errstr);
+        pfree(wstr);
+        return false;
+    }
+    pg_regfree(&re);
+    pfree(wstr);
+    return true;
+}
+
 static bool
 check_timezone_abbreviations(char **newval, void **extra, GucSource source)
 {
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 39fc787..5caeb76 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -682,6 +682,9 @@

 #dynamic_library_path = '$libdir'

+#trusted_extensions_dba = '^pl'        # exts installable by database owner
+#trusted_extensions_anyone = '^$'    # exts installable by anyone
+

 #------------------------------------------------------------------------------
 # LOCK MANAGEMENT
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b88e886..f2551e1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9431,9 +9431,9 @@
   proname => 'pg_available_extension_versions', procost => '10',
   prorows => '100', proretset => 't', provolatile => 's',
   prorettype => 'record', proargtypes => '',
-  proallargtypes => '{name,text,bool,bool,name,_name,text}',
-  proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,version,superuser,relocatable,schema,requires,comment}',
+  proallargtypes => '{name,text,bool,bool,bool,name,_name,text}',
+  proargmodes => '{o,o,o,o,o,o,o,o}',
+  proargnames => '{name,version,superuser,trustable,relocatable,schema,requires,comment}',
   prosrc => 'pg_available_extension_versions' },
 { oid => '3084', descr => 'list an extension\'s version update paths',
   proname => 'pg_extension_update_paths', procost => '10', prorows => '100',
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 02fc17d..3a155eaf 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -18,6 +18,10 @@
 #include "nodes/parsenodes.h"


+/* GUC settings */
+extern char *trusted_extensions_dba;
+extern char *trusted_extensions_anyone;
+
 /*
  * creating_extension is only true while running a CREATE EXTENSION or ALTER
  * EXTENSION UPDATE command.  It instructs recordDependencyOnCurrentExtension()
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 210e9cd..56b20f6 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1311,11 +1311,12 @@ pg_available_extension_versions| SELECT e.name,
     e.version,
     (x.extname IS NOT NULL) AS installed,
     e.superuser,
+    e.trustable,
     e.relocatable,
     e.schema,
     e.requires,
     e.comment
-   FROM (pg_available_extension_versions() e(name, version, superuser, relocatable, schema, requires, comment)
+   FROM (pg_available_extension_versions() e(name, version, superuser, trustable, relocatable, schema, requires,
comment)
      LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion))));
 pg_available_extensions| SELECT e.name,
     e.default_version,
diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile
index 9b1c514..e4d0a0b 100644
--- a/src/pl/plperl/GNUmakefile
+++ b/src/pl/plperl/GNUmakefile
@@ -55,8 +55,10 @@ endif # win32

 SHLIB_LINK = $(perl_embed_ldflags)

-REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-extension=plperl  --load-extension=plperlu
-REGRESS = plperl plperl_lc plperl_trigger plperl_shared plperl_elog plperl_util plperl_init plperlu plperl_array
plperl_callplperl_transaction 
+REGRESS_OPTS = --dbname=$(PL_TESTDB)
+REGRESS = plperl_setup plperl plperl_lc plperl_trigger plperl_shared \
+    plperl_elog plperl_util plperl_init plperlu plperl_array \
+    plperl_call plperl_transaction
 # if Perl can support two interpreters in one backend,
 # test plperl-and-plperlu cases
 ifneq ($(PERL),)
diff --git a/src/pl/plperl/expected/plperl_setup.out b/src/pl/plperl/expected/plperl_setup.out
new file mode 100644
index 0000000..05cf5ac
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_setup.out
@@ -0,0 +1,63 @@
+--
+-- Install the plperl and plperlu extensions
+--
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- the trusted-extensions configuration permits.
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+SET trusted_extensions_anyone = '^$';
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;  -- fail
+ERROR:  permission denied to create extension "plperl"
+HINT:  Must be superuser to create this extension.
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+SET trusted_extensions_anyone = '^plperl';  -- fail
+ERROR:  permission denied to set parameter "trusted_extensions_anyone"
+RESET ROLE;
+SET trusted_extensions_anyone = '^plperl';
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+ foo1
+------
+    1
+(1 row)
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+ERROR:  permission denied for language plperl
+SET ROLE regress_user1;
+grant usage on language plperl to regress_user2;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+ foo2
+------
+    2
+(1 row)
+
+SET ROLE regress_user1;
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+ERROR:  cannot drop language plperl because extension plperl requires it
+HINT:  You can drop extension plperl instead.
+DROP EXTENSION plperl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to function foo1()
+drop cascades to function foo2()
+-- Clean up
+RESET ROLE;
+DROP USER regress_user1;
+DROP USER regress_user2;
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plperl/plperl--1.0.sql b/src/pl/plperl/plperl--1.0.sql
index f716ba1..5ff31e7 100644
--- a/src/pl/plperl/plperl--1.0.sql
+++ b/src/pl/plperl/plperl--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plperl/plperl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperl;
+CREATE FUNCTION plperl_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperl_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plperl
+  HANDLER plperl_call_handler
+  INLINE plperl_inline_handler
+  VALIDATOR plperl_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plperl OWNER TO @extowner@;

 COMMENT ON LANGUAGE plperl IS 'PL/Perl procedural language';
diff --git a/src/pl/plperl/plperl.control b/src/pl/plperl/plperl.control
index 6faace1..1fb7e7e 100644
--- a/src/pl/plperl/plperl.control
+++ b/src/pl/plperl/plperl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plperl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trustable = true
diff --git a/src/pl/plperl/plperlu--1.0.sql b/src/pl/plperl/plperlu--1.0.sql
index 7efb4fb..10d7594 100644
--- a/src/pl/plperl/plperlu--1.0.sql
+++ b/src/pl/plperl/plperlu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plperl/plperlu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperlu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperlu;
+CREATE FUNCTION plperlu_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperlu_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plperlu
+  HANDLER plperlu_call_handler
+  INLINE plperlu_inline_handler
+  VALIDATOR plperlu_validator;

 COMMENT ON LANGUAGE plperlu IS 'PL/PerlU untrusted procedural language';
diff --git a/src/pl/plperl/sql/plperl_setup.sql b/src/pl/plperl/sql/plperl_setup.sql
new file mode 100644
index 0000000..dd292fb
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_setup.sql
@@ -0,0 +1,62 @@
+--
+-- Install the plperl and plperlu extensions
+--
+
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- the trusted-extensions configuration permits.
+
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+
+SET trusted_extensions_anyone = '^$';
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;  -- fail
+CREATE EXTENSION plperlu;  -- fail
+
+SET trusted_extensions_anyone = '^plperl';  -- fail
+
+RESET ROLE;
+
+SET trusted_extensions_anyone = '^plperl';
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+
+SET ROLE regress_user1;
+
+grant usage on language plperl to regress_user2;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+
+SET ROLE regress_user1;
+
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+DROP EXTENSION plperl CASCADE;
+
+-- Clean up
+RESET ROLE;
+DROP USER regress_user1;
+DROP USER regress_user2;
+
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plpgsql/src/plpgsql--1.0.sql b/src/pl/plpgsql/src/plpgsql--1.0.sql
index ab6fa84..6e5b990 100644
--- a/src/pl/plpgsql/src/plpgsql--1.0.sql
+++ b/src/pl/plpgsql/src/plpgsql--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plpgsql/src/plpgsql--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpgsql_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpgsql;
+CREATE FUNCTION plpgsql_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpgsql_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plpgsql
+  HANDLER plpgsql_call_handler
+  INLINE plpgsql_inline_handler
+  VALIDATOR plpgsql_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plpgsql OWNER TO @extowner@;

 COMMENT ON LANGUAGE plpgsql IS 'PL/pgSQL procedural language';
diff --git a/src/pl/plpgsql/src/plpgsql.control b/src/pl/plpgsql/src/plpgsql.control
index b320227..c4373c8 100644
--- a/src/pl/plpgsql/src/plpgsql.control
+++ b/src/pl/plpgsql/src/plpgsql.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plpgsql'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trustable = true
diff --git a/src/pl/plpython/plpython2u--1.0.sql b/src/pl/plpython/plpython2u--1.0.sql
index 661cc66..69f7477 100644
--- a/src/pl/plpython/plpython2u--1.0.sql
+++ b/src/pl/plpython/plpython2u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython2u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython2_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython2u;
+CREATE FUNCTION plpython2_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython2_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython2u
+  HANDLER plpython2_call_handler
+  INLINE plpython2_inline_handler
+  VALIDATOR plpython2_validator;

 COMMENT ON LANGUAGE plpython2u IS 'PL/Python2U untrusted procedural language';
diff --git a/src/pl/plpython/plpython3u--1.0.sql b/src/pl/plpython/plpython3u--1.0.sql
index c0d6ea8..ba2e6ac 100644
--- a/src/pl/plpython/plpython3u--1.0.sql
+++ b/src/pl/plpython/plpython3u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython3u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython3_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython3u;
+CREATE FUNCTION plpython3_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython3_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython3u
+  HANDLER plpython3_call_handler
+  INLINE plpython3_inline_handler
+  VALIDATOR plpython3_validator;

 COMMENT ON LANGUAGE plpython3u IS 'PL/Python3U untrusted procedural language';
diff --git a/src/pl/plpython/plpythonu--1.0.sql b/src/pl/plpython/plpythonu--1.0.sql
index 4a3e64a..4c6f7c3 100644
--- a/src/pl/plpython/plpythonu--1.0.sql
+++ b/src/pl/plpython/plpythonu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpythonu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpythonu;
+CREATE FUNCTION plpython_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpythonu
+  HANDLER plpython_call_handler
+  INLINE plpython_inline_handler
+  VALIDATOR plpython_validator;

 COMMENT ON LANGUAGE plpythonu IS 'PL/PythonU untrusted procedural language';
diff --git a/src/pl/tcl/pltcl--1.0.sql b/src/pl/tcl/pltcl--1.0.sql
index 34a68c8..2ed2b92 100644
--- a/src/pl/tcl/pltcl--1.0.sql
+++ b/src/pl/tcl/pltcl--1.0.sql
@@ -1,11 +1,12 @@
 /* src/pl/tcl/pltcl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltcl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltcl;
+CREATE TRUSTED LANGUAGE pltcl
+  HANDLER pltcl_call_handler;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE pltcl OWNER TO @extowner@;

 COMMENT ON LANGUAGE pltcl IS 'PL/Tcl procedural language';
diff --git a/src/pl/tcl/pltcl.control b/src/pl/tcl/pltcl.control
index b9dc1b8..fb242c4 100644
--- a/src/pl/tcl/pltcl.control
+++ b/src/pl/tcl/pltcl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/pltcl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trustable = true
diff --git a/src/pl/tcl/pltclu--1.0.sql b/src/pl/tcl/pltclu--1.0.sql
index e05b470..fca869f 100644
--- a/src/pl/tcl/pltclu--1.0.sql
+++ b/src/pl/tcl/pltclu--1.0.sql
@@ -1,11 +1,9 @@
 /* src/pl/tcl/pltclu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltclu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltclu;
+CREATE LANGUAGE pltclu
+  HANDLER pltclu_call_handler;

 COMMENT ON LANGUAGE pltclu IS 'PL/TclU untrusted procedural language';
diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
index 13b28b1..2a9c7e8 100644
--- a/doc/src/sgml/ref/create_language.sgml
+++ b/doc/src/sgml/ref/create_language.sgml
@@ -21,9 +21,9 @@ PostgreSQL documentation

  <refsynopsisdiv>
 <synopsis>
-CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable
class="parameter">inline_handler</replaceable>] [ VALIDATOR <replaceable>valfunction</replaceable> ] 
+CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 </synopsis>
  </refsynopsisdiv>

@@ -37,21 +37,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
    defined in this new language.
   </para>

-  <note>
-   <para>
-    As of <productname>PostgreSQL</productname> 9.1, most procedural
-    languages have been made into <quote>extensions</quote>, and should
-    therefore be installed with <xref linkend="sql-createextension"/>
-    not <command>CREATE LANGUAGE</command>.  Direct use of
-    <command>CREATE LANGUAGE</command> should now be confined to
-    extension installation scripts.  If you have a <quote>bare</quote>
-    language in your database, perhaps as a result of an upgrade,
-    you can convert it to an extension using
-    <literal>CREATE EXTENSION <replaceable>langname</replaceable> FROM
-    unpackaged</literal>.
-   </para>
-  </note>
-
   <para>
    <command>CREATE LANGUAGE</command> effectively associates the
    language name with handler function(s) that are responsible for executing
@@ -60,53 +45,32 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   There are two forms of the <command>CREATE LANGUAGE</command> command.
-   In the first form, the user supplies just the name of the desired
-   language, and the <productname>PostgreSQL</productname> server consults
-   the <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
-   system catalog to determine the correct parameters.  In the second form,
-   the user supplies the language parameters along with the language name.
-   The second form can be used to create a language that is not defined in
-   <structname>pg_pltemplate</structname>, but this approach is considered obsolescent.
-  </para>
-
-  <para>
-   When the server finds an entry in the <structname>pg_pltemplate</structname> catalog
-   for the given language name, it will use the catalog data even if the
-   command includes language parameters.  This behavior simplifies loading of
-   old dump files, which are likely to contain out-of-date information
-   about language support functions.
-  </para>
-
-  <para>
-   Ordinarily, the user must have the
-   <productname>PostgreSQL</productname> superuser privilege to
-   register a new language.  However, the owner of a database can register
-   a new language within that database if the language is listed in
-   the <structname>pg_pltemplate</structname> catalog and is marked
-   as allowed to be created by database owners (<structfield>tmpldbacreate</structfield>
-   is true).  The default is that trusted languages can be created
-   by database owners, but this can be adjusted by superusers by modifying
-   the contents of <structname>pg_pltemplate</structname>.
-   The creator of a language becomes its owner and can later
-   drop it, rename it, or assign it to a new owner.
+   The form of <command>CREATE LANGUAGE</command> that does not supply
+   any handler function is obsolete.  For backwards compatibility with
+   old dump files, it is interpreted as <command>CREATE EXTENSION</command>.
+   That will work if the language has been packaged into an extension of
+   the same name, which is the conventional way to set up procedural
+   languages.
   </para>

   <para>
    <command>CREATE OR REPLACE LANGUAGE</command> will either create a
    new language, or replace an existing definition.  If the language
-   already exists, its parameters are updated according to the values
-   specified or taken from <structname>pg_pltemplate</structname>,
+   already exists, its parameters are updated according to the command,
    but the language's ownership and permissions settings do not change,
    and any existing functions written in the language are assumed to still
-   be valid.  In addition to the normal privilege requirements for creating
-   a language, the user must be superuser or owner of the existing language.
-   The <literal>REPLACE</literal> case is mainly meant to be used to
-   ensure that the language exists.  If the language has a
-   <structname>pg_pltemplate</structname> entry then <literal>REPLACE</literal>
-   will not actually change anything about an existing definition, except in
-   the unusual case where the <structname>pg_pltemplate</structname> entry
-   has been modified since the language was created.
+   be valid.
+  </para>
+
+  <para>
+   One must have the
+   <productname>PostgreSQL</productname> superuser privilege to
+   register a new language or change an existing language's parameters.
+   However, once the language is created it is valid to assign ownership of
+   it to a non-superuser, who may then drop it, change its permissions,
+   rename it, or assign it to a new owner.  (Do not, however, assign
+   ownership of the underlying C functions to a non-superuser; that would
+   create a privilege escalation path for that user.)
   </para>
  </refsect1>

@@ -218,12 +182,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
      </listitem>
     </varlistentry>
    </variablelist>
-
-  <para>
-   The <literal>TRUSTED</literal> option and the support function name(s) are
-   ignored if the server has an entry for the specified language
-   name in <structname>pg_pltemplate</structname>.
-  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-notes">
@@ -255,18 +213,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   The call handler function, the inline handler function (if any),
-   and the validator function (if any)
-   must already exist if the server does not have an entry for the language
-   in <structname>pg_pltemplate</structname>.  But when there is an entry,
-   the functions need not already exist;
-   they will be automatically defined if not present in the database.
-   (This might result in <command>CREATE LANGUAGE</command> failing, if the
-   shared library that implements the language is not available in
-   the installation.)
-  </para>
-
-  <para>
    In <productname>PostgreSQL</productname> versions before 7.3, it was
    necessary to declare handler functions as returning the placeholder
    type <type>opaque</type>, rather than <type>language_handler</type>.
@@ -281,23 +227,20 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   <title>Examples</title>

   <para>
-   The preferred way of creating any of the standard procedural languages
-   is just:
-<programlisting>
-CREATE LANGUAGE plperl;
-</programlisting>
-  </para>
-
-  <para>
-   For a language not known in the <structname>pg_pltemplate</structname> catalog, a
-   sequence such as this is needed:
+   A minimal sequence for creating a new procedural language is:
 <programlisting>
 CREATE FUNCTION plsample_call_handler() RETURNS language_handler
     AS '$libdir/plsample'
     LANGUAGE C;
 CREATE LANGUAGE plsample
     HANDLER plsample_call_handler;
-</programlisting></para>
+</programlisting>
+   Typically that would be written in an extension's creation script,
+   and users would do this to install the extension:
+<programlisting>
+CREATE EXTENSION plsample;
+</programlisting>
+  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-compat">
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 257b4fc..8fcd98b 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2333,6 +2333,64 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 }

 /*
+ * Test whether the given extension exists (not whether it's installed)
+ *
+ * This checks for the existence of a matching control file in the extension
+ * directory.  That's not a bulletproof check, since the file might be
+ * invalid, but this is only used for hints so it doesn't have to be 100%
+ * right.
+ */
+bool
+extension_file_exists(const char *extensionName)
+{
+    bool        result = false;
+    char       *location;
+    DIR           *dir;
+    struct dirent *de;
+
+    location = get_extension_control_directory();
+    dir = AllocateDir(location);
+
+    /*
+     * If the control directory doesn't exist, we want to silently return
+     * false.  Any other error will be reported by ReadDir.
+     */
+    if (dir == NULL && errno == ENOENT)
+    {
+        /* do nothing */
+    }
+    else
+    {
+        while ((de = ReadDir(dir, location)) != NULL)
+        {
+            char       *extname;
+
+            if (!is_extension_control_filename(de->d_name))
+                continue;
+
+            /* extract extension name from 'name.control' filename */
+            extname = pstrdup(de->d_name);
+            *strrchr(extname, '.') = '\0';
+
+            /* ignore it if it's an auxiliary control file */
+            if (strstr(extname, "--"))
+                continue;
+
+            /* done if it matches request */
+            if (strcmp(extname, extensionName) == 0)
+            {
+                result = true;
+                break;
+            }
+        }
+
+        FreeDir(dir);
+    }
+
+    return result;
+}
+
+/*
  * Convert a list of extension names to a name[] Datum
  */
 static Datum
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 40f1f9a..aa408b8 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/extension.h"
 #include "commands/proclang.h"
 #include "executor/execdesc.h"
 #include "executor/executor.h"
@@ -991,7 +992,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_OBJECT),
                  errmsg("language \"%s\" does not exist", language),
-                 (PLTemplateExists(language) ?
+                 (extension_file_exists(language) ?
                   errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

     languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
@@ -2225,7 +2226,7 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_OBJECT),
                  errmsg("language \"%s\" does not exist", language),
-                 (PLTemplateExists(language) ?
+                 (extension_file_exists(language) ?
                   errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

     languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index 343cd1d..520a603 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -13,329 +13,110 @@
  */
 #include "postgres.h"

-#include "access/genam.h"
-#include "access/htup_details.h"
 #include "access/table.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
-#include "catalog/pg_authid.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
-#include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/proclang.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
-#include "parser/parser.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
-#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"


-typedef struct
-{
-    bool        tmpltrusted;    /* trusted? */
-    bool        tmpldbacreate;    /* db owner allowed to create? */
-    char       *tmplhandler;    /* name of handler function */
-    char       *tmplinline;        /* name of anonymous-block handler, or NULL */
-    char       *tmplvalidator;    /* name of validator function, or NULL */
-    char       *tmpllibrary;    /* path of shared library */
-} PLTemplate;
-
-static ObjectAddress create_proc_lang(const char *languageName, bool replace,
-                                      Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                                      Oid valOid, bool trusted);
-static PLTemplate *find_language_template(const char *languageName);
-
 /*
  * CREATE LANGUAGE
  */
 ObjectAddress
 CreateProceduralLanguage(CreatePLangStmt *stmt)
 {
-    PLTemplate *pltemplate;
-    ObjectAddress tmpAddr;
+    const char *languageName = stmt->plname;
+    Oid            languageOwner = GetUserId();
     Oid            handlerOid,
                 inlineOid,
                 valOid;
     Oid            funcrettype;
     Oid            funcargtypes[1];
+    Relation    rel;
+    TupleDesc    tupDesc;
+    Datum        values[Natts_pg_language];
+    bool        nulls[Natts_pg_language];
+    bool        replaces[Natts_pg_language];
+    NameData    langname;
+    HeapTuple    oldtup;
+    HeapTuple    tup;
+    Oid            langoid;
+    bool        is_update;
+    ObjectAddress myself,
+                referenced;

     /*
-     * If we have template information for the language, ignore the supplied
-     * parameters (if any) and use the template information.
+     * Check permission
      */
-    if ((pltemplate = find_language_template(stmt->plname)) != NULL)
-    {
-        List       *funcname;
-
-        /*
-         * Give a notice if we are ignoring supplied parameters.
-         */
-        if (stmt->plhandler)
-            ereport(NOTICE,
-                    (errmsg("using pg_pltemplate information instead of CREATE LANGUAGE parameters")));
-
-        /*
-         * Check permission
-         */
-        if (!superuser())
-        {
-            if (!pltemplate->tmpldbacreate)
-                ereport(ERROR,
-                        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                         errmsg("must be superuser to create procedural language \"%s\"",
-                                stmt->plname)));
-            if (!pg_database_ownercheck(MyDatabaseId, GetUserId()))
-                aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
-                               get_database_name(MyDatabaseId));
-        }
-
-        /*
-         * Find or create the handler function, which we force to be in the
-         * pg_catalog schema.  If already present, it must have the correct
-         * return type.
-         */
-        funcname = SystemFuncName(pltemplate->tmplhandler);
-        handlerOid = LookupFuncName(funcname, 0, funcargtypes, true);
-        if (OidIsValid(handlerOid))
-        {
-            funcrettype = get_func_rettype(handlerOid);
-            if (funcrettype != LANGUAGE_HANDLEROID)
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(funcname), "language_handler")));
-        }
-        else
-        {
-            tmpAddr = ProcedureCreate(pltemplate->tmplhandler,
-                                      PG_CATALOG_NAMESPACE,
-                                      false,    /* replace */
-                                      false,    /* returnsSet */
-                                      LANGUAGE_HANDLEROID,
-                                      BOOTSTRAP_SUPERUSERID,
-                                      ClanguageId,
-                                      F_FMGR_C_VALIDATOR,
-                                      pltemplate->tmplhandler,
-                                      pltemplate->tmpllibrary,
-                                      PROKIND_FUNCTION,
-                                      false,    /* security_definer */
-                                      false,    /* isLeakProof */
-                                      false,    /* isStrict */
-                                      PROVOLATILE_VOLATILE,
-                                      PROPARALLEL_UNSAFE,
-                                      buildoidvector(funcargtypes, 0),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      NIL,
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      InvalidOid,
-                                      1,
-                                      0);
-            handlerOid = tmpAddr.objectId;
-        }
-
-        /*
-         * Likewise for the anonymous block handler, if required; but we don't
-         * care about its return type.
-         */
-        if (pltemplate->tmplinline)
-        {
-            funcname = SystemFuncName(pltemplate->tmplinline);
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(inlineOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplinline,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplinline,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                inlineOid = tmpAddr.objectId;
-            }
-        }
-        else
-            inlineOid = InvalidOid;
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 errmsg("must be superuser to create custom procedural language")));

+    /*
+     * Lookup the PL handler function and check that it is of the expected
+     * return type
+     */
+    Assert(stmt->plhandler);
+    handlerOid = LookupFuncName(stmt->plhandler, 0, funcargtypes, false);
+    funcrettype = get_func_rettype(handlerOid);
+    if (funcrettype != LANGUAGE_HANDLEROID)
+    {
         /*
-         * Likewise for the validator, if required; but we don't care about
-         * its return type.
+         * We allow OPAQUE just so we can load old dump files.  When we see a
+         * handler function declared OPAQUE, change it to LANGUAGE_HANDLER.
+         * (This is probably obsolete and removable?)
          */
-        if (pltemplate->tmplvalidator)
+        if (funcrettype == OPAQUEOID)
         {
-            funcname = SystemFuncName(pltemplate->tmplvalidator);
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(valOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplvalidator,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplvalidator,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                valOid = tmpAddr.objectId;
-            }
+            ereport(WARNING,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("changing return type of function %s from %s to %s",
+                            NameListToString(stmt->plhandler),
+                            "opaque", "language_handler")));
+            SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
         }
         else
-            valOid = InvalidOid;
+            ereport(ERROR,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("function %s must return type %s",
+                            NameListToString(stmt->plhandler), "language_handler")));
+    }

-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, pltemplate->tmpltrusted);
+    /* validate the inline function */
+    if (stmt->plinline)
+    {
+        funcargtypes[0] = INTERNALOID;
+        inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
     else
-    {
-        /*
-         * No template, so use the provided information.  If there's no
-         * handler clause, the user is trying to rely on a template that we
-         * don't have, so complain accordingly.
-         */
-        if (!stmt->plhandler)
-            ereport(ERROR,
-                    (errcode(ERRCODE_UNDEFINED_OBJECT),
-                     errmsg("unsupported language \"%s\"",
-                            stmt->plname),
-                     errhint("The supported languages are listed in the pg_pltemplate system catalog.")));
+        inlineOid = InvalidOid;

-        /*
-         * Check permission
-         */
-        if (!superuser())
-            ereport(ERROR,
-                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                     errmsg("must be superuser to create custom procedural language")));
-
-        /*
-         * Lookup the PL handler function and check that it is of the expected
-         * return type
-         */
-        handlerOid = LookupFuncName(stmt->plhandler, 0, funcargtypes, false);
-        funcrettype = get_func_rettype(handlerOid);
-        if (funcrettype != LANGUAGE_HANDLEROID)
-        {
-            /*
-             * We allow OPAQUE just so we can load old dump files.  When we
-             * see a handler function declared OPAQUE, change it to
-             * LANGUAGE_HANDLER.  (This is probably obsolete and removable?)
-             */
-            if (funcrettype == OPAQUEOID)
-            {
-                ereport(WARNING,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("changing return type of function %s from %s to %s",
-                                NameListToString(stmt->plhandler),
-                                "opaque", "language_handler")));
-                SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
-            }
-            else
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(stmt->plhandler), "language_handler")));
-        }
-
-        /* validate the inline function */
-        if (stmt->plinline)
-        {
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            inlineOid = InvalidOid;
-
-        /* validate the validator function */
-        if (stmt->plvalidator)
-        {
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            valOid = InvalidOid;
-
-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, stmt->pltrusted);
+    /* validate the validator function */
+    if (stmt->plvalidator)
+    {
+        funcargtypes[0] = OIDOID;
+        valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
-}
-
-/*
- * Guts of language creation.
- */
-static ObjectAddress
-create_proc_lang(const char *languageName, bool replace,
-                 Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                 Oid valOid, bool trusted)
-{
-    Relation    rel;
-    TupleDesc    tupDesc;
-    Datum        values[Natts_pg_language];
-    bool        nulls[Natts_pg_language];
-    bool        replaces[Natts_pg_language];
-    NameData    langname;
-    HeapTuple    oldtup;
-    HeapTuple    tup;
-    Oid            langoid;
-    bool        is_update;
-    ObjectAddress myself,
-                referenced;
+    else
+        valOid = InvalidOid;

+    /* ok to create it */
     rel = table_open(LanguageRelationId, RowExclusiveLock);
     tupDesc = RelationGetDescr(rel);

@@ -348,7 +129,7 @@ create_proc_lang(const char *languageName, bool replace,
     values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname);
     values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner);
     values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true);
-    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(trusted);
+    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(stmt->pltrusted);
     values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
     values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
     values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
@@ -362,13 +143,17 @@ create_proc_lang(const char *languageName, bool replace,
         Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup);

         /* There is one; okay to replace it? */
-        if (!replace)
+        if (!stmt->replace)
             ereport(ERROR,
                     (errcode(ERRCODE_DUPLICATE_OBJECT),
                      errmsg("language \"%s\" already exists", languageName)));
+
+        /* This is currently pointless, since we already checked superuser */
+#ifdef NOT_USED
         if (!pg_language_ownercheck(oldform->oid, languageOwner))
             aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE,
                            languageName);
+#endif

         /*
          * Do not change existing oid, ownership or permissions.  Note
@@ -451,83 +236,6 @@ create_proc_lang(const char *languageName, bool replace,
 }

 /*
- * Look to see if we have template information for the given language name.
- */
-static PLTemplate *
-find_language_template(const char *languageName)
-{
-    PLTemplate *result;
-    Relation    rel;
-    SysScanDesc scan;
-    ScanKeyData key;
-    HeapTuple    tup;
-
-    rel = table_open(PLTemplateRelationId, AccessShareLock);
-
-    ScanKeyInit(&key,
-                Anum_pg_pltemplate_tmplname,
-                BTEqualStrategyNumber, F_NAMEEQ,
-                CStringGetDatum(languageName));
-    scan = systable_beginscan(rel, PLTemplateNameIndexId, true,
-                              NULL, 1, &key);
-
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_pltemplate tmpl = (Form_pg_pltemplate) GETSTRUCT(tup);
-        Datum        datum;
-        bool        isnull;
-
-        result = (PLTemplate *) palloc0(sizeof(PLTemplate));
-        result->tmpltrusted = tmpl->tmpltrusted;
-        result->tmpldbacreate = tmpl->tmpldbacreate;
-
-        /* Remaining fields are variable-width so we need heap_getattr */
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplhandler,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplhandler = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplinline,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplinline = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplvalidator,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplvalidator = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmpllibrary = TextDatumGetCString(datum);
-
-        /* Ignore template if handler or library info is missing */
-        if (!result->tmplhandler || !result->tmpllibrary)
-            result = NULL;
-    }
-    else
-        result = NULL;
-
-    systable_endscan(scan);
-
-    table_close(rel, AccessShareLock);
-
-    return result;
-}
-
-
-/*
- * This just returns true if we have a valid template for a given language
- */
-bool
-PLTemplateExists(const char *languageName)
-{
-    return (find_language_template(languageName) != NULL);
-}
-
-/*
  * Guts of language dropping.
  */
 void
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb36..d09b4a5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4273,14 +4273,17 @@ NumericOnly_list:    NumericOnly                        { $$ = list_make1($1); }
 CreatePLangStmt:
             CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
             {
-                CreatePLangStmt *n = makeNode(CreatePLangStmt);
-                n->replace = $2;
-                n->plname = $6;
-                /* parameters are all to be supplied by system */
-                n->plhandler = NIL;
-                n->plinline = NIL;
-                n->plvalidator = NIL;
-                n->pltrusted = false;
+                /*
+                 * We now interpret parameterless CREATE LANGUAGE as
+                 * CREATE EXTENSION.  "OR REPLACE" is silently translated
+                 * to "IF NOT EXISTS", which isn't quite the same, but
+                 * seems more useful than throwing an error.  We just
+                 * ignore TRUSTED, as the previous code would have too.
+                 */
+                CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
+                n->if_not_exists = $2;
+                n->extname = $6;
+                n->options = NIL;
                 $$ = (Node *)n;
             }
             | CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 3a155eaf..b4e3c65 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -51,6 +51,7 @@ extern ObjectAddress ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *

 extern Oid    get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
+extern bool extension_file_exists(const char *extensionName);

 extern ObjectAddress AlterExtensionNamespace(const char *extensionName, const char *newschema,
                                              Oid *oldschema);
diff --git a/src/include/commands/proclang.h b/src/include/commands/proclang.h
index 9a4bc75..d7b0ab5 100644
--- a/src/include/commands/proclang.h
+++ b/src/include/commands/proclang.h
@@ -1,11 +1,12 @@
-/*
- * src/include/commands/proclang.h
- *
- *-------------------------------------------------------------------------
+/*-------------------------------------------------------------------------
  *
  * proclang.h
  *      prototypes for proclang.c.
  *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/proclang.h
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +18,7 @@

 extern ObjectAddress CreateProceduralLanguage(CreatePLangStmt *stmt);
 extern void DropProceduralLanguageById(Oid langOid);
-extern bool PLTemplateExists(const char *languageName);
+
 extern Oid    get_language_oid(const char *langname, bool missing_ok);

 #endif                            /* PROCLANG_H */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4c32fd8..4de7075 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,11 +226,6 @@
      </row>

      <row>
-      <entry><link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link></entry>
-      <entry>template data for procedural languages</entry>
-     </row>
-
-     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -4911,113 +4906,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
  </sect1>


- <sect1 id="catalog-pg-pltemplate">
-  <title><structname>pg_pltemplate</structname></title>
-
-  <indexterm zone="catalog-pg-pltemplate">
-   <primary>pg_pltemplate</primary>
-  </indexterm>
-
-  <para>
-   The catalog <structname>pg_pltemplate</structname> stores
-   <quote>template</quote> information for procedural languages.
-   A template for a language allows the language to be created in a
-   particular database by a simple <command>CREATE LANGUAGE</command> command,
-   with no need to specify implementation details.
-  </para>
-
-  <para>
-   Unlike most system catalogs, <structname>pg_pltemplate</structname>
-   is shared across all databases of a cluster: there is only one
-   copy of <structname>pg_pltemplate</structname> per cluster, not
-   one per database.  This allows the information to be accessible in
-   each database as it is needed.
-  </para>
-
-  <table>
-   <title><structname>pg_pltemplate</structname> Columns</title>
-
-   <tgroup cols="3">
-    <thead>
-     <row>
-      <entry>Name</entry>
-      <entry>Type</entry>
-      <entry>Description</entry>
-     </row>
-    </thead>
-
-    <tbody>
-     <row>
-      <entry><structfield>tmplname</structfield></entry>
-      <entry><type>name</type></entry>
-      <entry>Name of the language this template is for</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpltrusted</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language is considered trusted</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpldbacreate</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language may be created by a database owner</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplhandler</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of call handler function</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplinline</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of anonymous-block handler function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplvalidator</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of validator function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpllibrary</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Path of shared library that implements language</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplacl</structfield></entry>
-      <entry><type>aclitem[]</type></entry>
-      <entry>Access privileges for template (not actually used)</entry>
-     </row>
-
-    </tbody>
-   </tgroup>
-  </table>
-
-  <para>
-   There are not currently any commands that manipulate procedural language
-   templates; to change the built-in information, a superuser must modify
-   the table using ordinary <command>INSERT</command>, <command>DELETE</command>,
-   or <command>UPDATE</command> commands.
-  </para>
-
-  <note>
-   <para>
-    It is likely that <structname>pg_pltemplate</structname> will be removed in some
-    future release of <productname>PostgreSQL</productname>, in favor of
-    keeping this knowledge about procedural languages in their respective
-    extension installation scripts.
-   </para>
-  </note>
-
- </sect1>
-
-
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>

diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 60a5907..b5109f0 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -153,7 +153,7 @@
      <para>
       Daredevils, who want to build a Python-3-only operating system
       environment, can change the contents of
-      <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
+      <literal>plpythonu</literal>'s extension control and script files
       to make <literal>plpythonu</literal> be equivalent
       to <literal>plpython3u</literal>, keeping in mind that this
       would make their installation incompatible with most of the rest
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 8bece07..0299ee0 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -37,7 +37,7 @@ CATALOG_HEADERS := \
     pg_statistic_ext.h pg_statistic_ext_data.h \
     pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
     pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
-    pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
+    pg_database.h pg_db_role_setting.h pg_tablespace.h \
     pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
     pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
     pg_ts_parser.h pg_ts_template.h pg_extension.h \
@@ -63,7 +63,7 @@ POSTGRES_BKI_DATA = $(addprefix $(top_srcdir)/src/include/catalog/,\
     pg_cast.dat pg_class.dat pg_collation.dat pg_conversion.dat \
     pg_database.dat pg_language.dat \
     pg_namespace.dat pg_opclass.dat pg_operator.dat pg_opfamily.dat \
-    pg_pltemplate.dat pg_proc.dat pg_range.dat pg_tablespace.dat \
+    pg_proc.dat pg_range.dat pg_tablespace.dat \
     pg_ts_config.dat pg_ts_config_map.dat pg_ts_dict.dat pg_ts_parser.dat \
     pg_ts_template.dat pg_type.dat \
     )
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 1af31c2..8d424de 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -32,7 +32,6 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
@@ -243,7 +242,6 @@ IsSharedRelation(Oid relationId)
     if (relationId == AuthIdRelationId ||
         relationId == AuthMemRelationId ||
         relationId == DatabaseRelationId ||
-        relationId == PLTemplateRelationId ||
         relationId == SharedDescriptionRelationId ||
         relationId == SharedDependRelationId ||
         relationId == SharedSecLabelRelationId ||
@@ -259,7 +257,6 @@ IsSharedRelation(Oid relationId)
         relationId == AuthMemMemRoleIndexId ||
         relationId == DatabaseNameIndexId ||
         relationId == DatabaseOidIndexId ||
-        relationId == PLTemplateNameIndexId ||
         relationId == SharedDescriptionObjIndexId ||
         relationId == SharedDependDependerIndexId ||
         relationId == SharedDependReferenceIndexId ||
@@ -279,8 +276,6 @@ IsSharedRelation(Oid relationId)
         relationId == PgDatabaseToastIndex ||
         relationId == PgDbRoleSettingToastTable ||
         relationId == PgDbRoleSettingToastIndex ||
-        relationId == PgPlTemplateToastTable ||
-        relationId == PgPlTemplateToastIndex ||
         relationId == PgReplicationOriginToastTable ||
         relationId == PgReplicationOriginToastIndex ||
         relationId == PgShdescriptionToastTable ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3498140..778000d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11352,9 +11352,9 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
     }

     /*
-     * If the functions are dumpable then emit a traditional CREATE LANGUAGE
-     * with parameters.  Otherwise, we'll write a parameterless command, which
-     * will rely on data from pg_pltemplate.
+     * If the functions are dumpable then emit a complete CREATE LANGUAGE with
+     * parameters.  Otherwise, we'll write a parameterless command, which will
+     * be interpreted as CREATE EXTENSION.
      */
     useParams = (funcInfo != NULL &&
                  (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
@@ -11387,11 +11387,11 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
         /*
          * If not dumping parameters, then use CREATE OR REPLACE so that the
          * command will not fail if the language is preinstalled in the target
-         * database.  We restrict the use of REPLACE to this case so as to
-         * eliminate the risk of replacing a language with incompatible
-         * parameter settings: this command will only succeed at all if there
-         * is a pg_pltemplate entry, and if there is one, the existing entry
-         * must match it too.
+         * database.
+         *
+         * Modern servers will interpret this as CREATE EXTENSION IF NOT
+         * EXISTS; perhaps we should emit that instead?  But it might just add
+         * confusion.
          */
         appendPQExpBuffer(defqry, "CREATE OR REPLACE PROCEDURAL LANGUAGE %s",
                           qlanname);
diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c
index 0c66d1c..d00d748 100644
--- a/src/bin/pg_upgrade/function.c
+++ b/src/bin/pg_upgrade/function.c
@@ -216,13 +216,9 @@ check_loadable_libraries(void)
              * plpython2u language was created with library name plpython2.so
              * as a symbolic link to plpython.so.  In Postgres 9.1, only the
              * plpython2.so library was created, and both plpythonu and
-             * plpython2u pointing to it.  For this reason, any reference to
+             * plpython2u point to it.  For this reason, any reference to
              * library name "plpython" in an old PG <= 9.1 cluster must look
              * for "plpython2" in the new cluster.
-             *
-             * For this case, we could check pg_pltemplate, but that only
-             * works for languages, and does not help with function shared
-             * objects, so we just do a general fix.
              */
             if (GET_MAJOR_VERSION(old_cluster.major_version) < 901 &&
                 strcmp(lib, "$libdir/plpython") == 0)
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ef4445b..7294d59 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -206,9 +206,6 @@ DECLARE_UNIQUE_INDEX(pg_opfamily_am_name_nsp_index, 2754, on pg_opfamily using b
 DECLARE_UNIQUE_INDEX(pg_opfamily_oid_index, 2755, on pg_opfamily using btree(oid oid_ops));
 #define OpfamilyOidIndexId    2755

-DECLARE_UNIQUE_INDEX(pg_pltemplate_name_index, 1137, on pg_pltemplate using btree(tmplname name_ops));
-#define PLTemplateNameIndexId  1137
-
 DECLARE_UNIQUE_INDEX(pg_proc_oid_index, 2690, on pg_proc using btree(oid oid_ops));
 #define ProcedureOidIndexId  2690
 DECLARE_UNIQUE_INDEX(pg_proc_proname_args_nsp_index, 2691, on pg_proc using btree(proname name_ops, proargtypes
oidvector_ops,pronamespace oid_ops)); 
diff --git a/src/include/catalog/pg_pltemplate.dat b/src/include/catalog/pg_pltemplate.dat
deleted file mode 100644
index 6f6d167..0000000
--- a/src/include/catalog/pg_pltemplate.dat
+++ /dev/null
@@ -1,51 +0,0 @@
-#----------------------------------------------------------------------
-#
-# pg_pltemplate.dat
-#    Initial contents of the pg_pltemplate system catalog.
-#
-# Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
-# Portions Copyright (c) 1994, Regents of the University of California
-#
-# src/include/catalog/pg_pltemplate.dat
-#
-#----------------------------------------------------------------------
-
-[
-
-{ tmplname => 'plpgsql', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plpgsql_call_handler', tmplinline => 'plpgsql_inline_handler',
-  tmplvalidator => 'plpgsql_validator', tmpllibrary => '$libdir/plpgsql',
-  tmplacl => '_null_' },
-{ tmplname => 'pltcl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'pltcl_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'pltclu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'pltclu_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plperl_call_handler', tmplinline => 'plperl_inline_handler',
-  tmplvalidator => 'plperl_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperlu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plperlu_call_handler', tmplinline => 'plperlu_inline_handler',
-  tmplvalidator => 'plperlu_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plpythonu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython_call_handler',
-  tmplinline => 'plpython_inline_handler',
-  tmplvalidator => 'plpython_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython2u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython2_call_handler',
-  tmplinline => 'plpython2_inline_handler',
-  tmplvalidator => 'plpython2_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython3u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython3_call_handler',
-  tmplinline => 'plpython3_inline_handler',
-  tmplvalidator => 'plpython3_validator', tmpllibrary => '$libdir/plpython3',
-  tmplacl => '_null_' },
-
-]
diff --git a/src/include/catalog/pg_pltemplate.h b/src/include/catalog/pg_pltemplate.h
deleted file mode 100644
index ce89000..0000000
--- a/src/include/catalog/pg_pltemplate.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * pg_pltemplate.h
- *      definition of the "PL template" system catalog (pg_pltemplate)
- *
- *
- * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/catalog/pg_pltemplate.h
- *
- * NOTES
- *      The Catalog.pm module reads this file and derives schema
- *      information.
- *
- *-------------------------------------------------------------------------
- */
-#ifndef PG_PLTEMPLATE_H
-#define PG_PLTEMPLATE_H
-
-#include "catalog/genbki.h"
-#include "catalog/pg_pltemplate_d.h"
-
-/* ----------------
- *        pg_pltemplate definition.  cpp turns this into
- *        typedef struct FormData_pg_pltemplate
- * ----------------
- */
-CATALOG(pg_pltemplate,1136,PLTemplateRelationId) BKI_SHARED_RELATION
-{
-    NameData    tmplname;        /* name of PL */
-    bool        tmpltrusted;    /* PL is trusted? */
-    bool        tmpldbacreate;    /* PL is installable by db owner? */
-
-#ifdef CATALOG_VARLEN            /* variable-length fields start here */
-    text        tmplhandler BKI_FORCE_NOT_NULL; /* name of call handler
-                                                 * function */
-    text        tmplinline;        /* name of anonymous-block handler, or NULL */
-    text        tmplvalidator;    /* name of validator function, or NULL */
-    text        tmpllibrary BKI_FORCE_NOT_NULL; /* path of shared library */
-    aclitem        tmplacl[1];        /* access privileges for template */
-#endif
-} FormData_pg_pltemplate;
-
-/* ----------------
- *        Form_pg_pltemplate corresponds to a pointer to a row with
- *        the format of pg_pltemplate relation.
- * ----------------
- */
-typedef FormData_pg_pltemplate *Form_pg_pltemplate;
-
-#endif                            /* PG_PLTEMPLATE_H */
diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h
index cc5dfed..5a047fc 100644
--- a/src/include/catalog/toasting.h
+++ b/src/include/catalog/toasting.h
@@ -86,9 +86,6 @@ DECLARE_TOAST(pg_database, 4177, 4178);
 DECLARE_TOAST(pg_db_role_setting, 2966, 2967);
 #define PgDbRoleSettingToastTable 2966
 #define PgDbRoleSettingToastIndex 2967
-DECLARE_TOAST(pg_pltemplate, 4179, 4180);
-#define PgPlTemplateToastTable 4179
-#define PgPlTemplateToastIndex 4180
 DECLARE_TOAST(pg_replication_origin, 4181, 4182);
 #define PgReplicationOriginToastTable 4181
 #define PgReplicationOriginToastIndex 4182
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 6edfcf2..0d9564e 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -33,7 +33,7 @@
  */

 #if PY_MAJOR_VERSION >= 3
-/* Use separate names to avoid clash in pg_pltemplate */
+/* Use separate names to reduce confusion */
 #define plpython_validator plpython3_validator
 #define plpython_call_handler plpython3_call_handler
 #define plpython_inline_handler plpython3_inline_handler
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d6e75ff..9bdba7b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -134,7 +134,6 @@ pg_opclass|t
 pg_operator|t
 pg_opfamily|t
 pg_partitioned_table|t
-pg_pltemplate|t
 pg_policy|t
 pg_proc|t
 pg_publication|t

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Peter Eisentraut
Date:
On 2019-08-21 21:29, Tom Lane wrote:
> Patch 0001 below addresses this problem by inventing a concept of
> "trustable" (not necessarily trusted) extensions.  An extension that
> would normally require superuser permissions (e.g., because it creates
> C functions) can now be installed by a non-superuser if (a) it is
> marked trustable in the extension's control file, AND (b) it is
> listed as trusted in one of two new GUCs, trusted_extensions_dba and
> trusted_extensions_anyone.  (These names could stand a visit to the
> bikeshed, no doubt.)  Extensions matching trusted_extensions_dba can
> be installed by a database owner, while extensions matching
> trusted_extensions_anyone can be installed by anybody.  The default
> settings of these GUCs provide backwards-compatible behavior, but
> they can be adjusted to provide more or less ability to install
> extensions.  (This design is basically what Andres advocated in [2].)

I think this overall direction is good.  I'm not so fond of the interfaces.

Using GUCs to control some of this creates yet another place where 
permission information is kept, and with it questions about how to get 
to it, how to edit it, or to back it up and restore it, etc.  Also, 
list-based parameters are particularly hard to manage by automated 
tools.  I think we can do this within the existing permission system, 
for example with pre-defined roles (for example, GRANT 
pg_create_trusted_extension ...).  Also, CREATE EXTENSION should somehow 
be controlled by the CREATE privilege on the containing database, so a 
separate setting for database owner vs. regular user might not be 
necessary.  Regular users would need both the role membership (given by 
the overall superuser) and the privilege within the database (given by 
the database owner).

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
> On 2019-08-21 21:29, Tom Lane wrote:
>> Patch 0001 below addresses this problem by inventing a concept of
>> "trustable" (not necessarily trusted) extensions.  An extension that
>> would normally require superuser permissions (e.g., because it creates
>> C functions) can now be installed by a non-superuser if (a) it is
>> marked trustable in the extension's control file, AND (b) it is
>> listed as trusted in one of two new GUCs, trusted_extensions_dba and
>> trusted_extensions_anyone.

> I think this overall direction is good.  I'm not so fond of the interfaces.

> Using GUCs to control some of this creates yet another place where 
> permission information is kept, and with it questions about how to get 
> to it, how to edit it, or to back it up and restore it, etc.  Also, 
> list-based parameters are particularly hard to manage by automated 
> tools.  I think we can do this within the existing permission system, 
> for example with pre-defined roles (for example, GRANT 
> pg_create_trusted_extension ...).  Also, CREATE EXTENSION should somehow 
> be controlled by the CREATE privilege on the containing database, so a 
> separate setting for database owner vs. regular user might not be 
> necessary.  Regular users would need both the role membership (given by 
> the overall superuser) and the privilege within the database (given by 
> the database owner).

Hm.  In principle I'm okay with the idea of having a predefined role
or two for extension installation.  I think though that we could not
easily make that design emulate the current behavior, wherein database
owners automatically have the ability to install trusted PLs.  The
superuser would have to take the additional step of granting them a
role to let them do that.  Maybe that's just fine --- from some
angles it could be seen as an improvement --- but it is an
incompatibility.  Anybody have a problem with that?

Do we need more than one level of extension trust-ability (and more
than one predefined role to go with that)?  Assuming that we go ahead
and mark all the safe-looking contrib modules as trustable, granting
"pg_install_extensions" or whatever we call it would then give a DB
owner more privilege than just the ability to install trusted PLs.
But maybe that's fine too.

I agree with the idea of requiring a DB-level privilege as well as
the overall role.  Is it okay to re-use the CREATE privilege (which
today only allows for CREATE SCHEMA), or do we need another one?
I think re-using CREATE is probably all right, since it would only be
useful for this purpose to users who also have "pg_install_extensions".

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
> > On 2019-08-21 21:29, Tom Lane wrote:
> >> Patch 0001 below addresses this problem by inventing a concept of
> >> "trustable" (not necessarily trusted) extensions.  An extension that
> >> would normally require superuser permissions (e.g., because it creates
> >> C functions) can now be installed by a non-superuser if (a) it is
> >> marked trustable in the extension's control file, AND (b) it is
> >> listed as trusted in one of two new GUCs, trusted_extensions_dba and
> >> trusted_extensions_anyone.
>
> > I think this overall direction is good.  I'm not so fond of the interfaces.

I'm not really thrilled with this interface either.

> > Using GUCs to control some of this creates yet another place where
> > permission information is kept, and with it questions about how to get
> > to it, how to edit it, or to back it up and restore it, etc.  Also,
> > list-based parameters are particularly hard to manage by automated
> > tools.  I think we can do this within the existing permission system,
> > for example with pre-defined roles (for example, GRANT
> > pg_create_trusted_extension ...).  Also, CREATE EXTENSION should somehow
> > be controlled by the CREATE privilege on the containing database, so a
> > separate setting for database owner vs. regular user might not be
> > necessary.  Regular users would need both the role membership (given by
> > the overall superuser) and the privilege within the database (given by
> > the database owner).

Two things- first, this doesn't actually cover everything that the
proposed GUCs do- specifically, the proposed GUCs give you a way to
limit what specific extensions are allowed to be installed, and by whom.
Moving to a GRANT-based system removes the extension specificity and
leaves with just "is user X allowed to install extensions".  Second,
this approach is requiring that a user who is allowed to create
extensions must also be allowed to create schemas on the database in
question.

> Hm.  In principle I'm okay with the idea of having a predefined role
> or two for extension installation.  I think though that we could not
> easily make that design emulate the current behavior, wherein database
> owners automatically have the ability to install trusted PLs.  The
> superuser would have to take the additional step of granting them a
> role to let them do that.  Maybe that's just fine --- from some
> angles it could be seen as an improvement --- but it is an
> incompatibility.  Anybody have a problem with that?

I'm certainly fine with a little backwards incompatibility breakage to
remove pg_pltemplate.

> Do we need more than one level of extension trust-ability (and more
> than one predefined role to go with that)?  Assuming that we go ahead
> and mark all the safe-looking contrib modules as trustable, granting
> "pg_install_extensions" or whatever we call it would then give a DB
> owner more privilege than just the ability to install trusted PLs.
> But maybe that's fine too.

I also agree with the idea of making PLs be closer to extensions, and
this change would move us in that direction too.

> I agree with the idea of requiring a DB-level privilege as well as
> the overall role.  Is it okay to re-use the CREATE privilege (which
> today only allows for CREATE SCHEMA), or do we need another one?

If we just created another one, wouldn't that remove the need to have a
database role?  I certainly understand that default roles in the
database are useful, but I don't think we should be using them in cases
where a traditional GRANT-based privilege could be used instead, and
this certainly seems like a case where a user could just have "CREATE
EXTENSION" as a privilege GRANT'd to their role, at a database level,
and they would then be able to create (trusted) extensions in that
database.  That would also make it independent of the "CREATE SCHEMA"
privilege that we have now, removing the need to wonder about the above
question regarding combining the two.

This is far from the first time we've talked about allowing privilege
based control over who is allowed to create what kind of objects in the
system.  That kind of fine-grained control over other objects would also
be a good improvement to our privilege system (not everyone needs to be
able to create functions and operators, particularly when those are
actually roles that are logged into by services who shouldn't ever be
creating those kinds of objects even if they, maybe, need to create
tables or similar...).

> I think re-using CREATE is probably all right, since it would only be
> useful for this purpose to users who also have "pg_install_extensions".

With this, you couldn't have a user who is able to create extensions but
not able to create schemas though.  That kind of combining of privileges
together really goes against the general principle of 'least privilege',
unless the action associated with one necessairly requires the other,
but I don't believe that's the case here.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Stephen Frost <sfrost@snowman.net> writes:
>> Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
>>> Using GUCs to control some of this creates yet another place where 
>>> permission information is kept, and with it questions about how to get 
>>> to it, how to edit it, or to back it up and restore it, etc.  Also, 
>>> list-based parameters are particularly hard to manage by automated 
>>> tools.  I think we can do this within the existing permission system, 
>>> for example with pre-defined roles (for example, GRANT 
>>> pg_create_trusted_extension ...).  Also, CREATE EXTENSION should somehow 
>>> be controlled by the CREATE privilege on the containing database, so a 
>>> separate setting for database owner vs. regular user might not be 
>>> necessary.  Regular users would need both the role membership (given by 
>>> the overall superuser) and the privilege within the database (given by 
>>> the database owner).

> Two things- first, this doesn't actually cover everything that the
> proposed GUCs do- specifically, the proposed GUCs give you a way to
> limit what specific extensions are allowed to be installed, and by whom.
> Moving to a GRANT-based system removes the extension specificity and
> leaves with just "is user X allowed to install extensions".

True.  But do we care?  We did not have that flexibility before, either.
I'd still keep the "trustable" property (probably renamed to "trusted"
for simplicity) for extensions, so in the worst case, an admin could
edit extension control files to add or remove the per-extension flag.

> Second,
> this approach is requiring that a user who is allowed to create
> extensions must also be allowed to create schemas on the database in
> question.

That doesn't seem like a big objection from here.  We could fix it
by making a separate privilege bit, but I doubt that it's worth using
up one of our limited set of spare bits for.

>> I agree with the idea of requiring a DB-level privilege as well as
>> the overall role.  Is it okay to re-use the CREATE privilege (which
>> today only allows for CREATE SCHEMA), or do we need another one?

> If we just created another one, wouldn't that remove the need to have a
> database role?

No, because then being DB owner would be alone be enough to let you
install extensions (since as owner, you could certainly grant yourself
all privileges on the DB, even if this were somehow not the default).
We'd have to mangle GRANT's behavior to avoid that, and I don't think
we should.  Nor do I think that DB ownership ought to be enough
privilege by itself.

>> I think re-using CREATE is probably all right, since it would only be
>> useful for this purpose to users who also have "pg_install_extensions".

> With this, you couldn't have a user who is able to create extensions but
> not able to create schemas though.  That kind of combining of privileges
> together really goes against the general principle of 'least privilege',
> unless the action associated with one necessairly requires the other,
> but I don't believe that's the case here.

A point here is that many extensions involve creating their own schemas
anyway.  Also, the ability to "relocate" an extension to a different
schema is pretty meaningless if you can't create a schema to put it in.

If I thought that there were a use-case for letting someone create
extensions but not schemas, I'd be more eager to invent a new bit.
But I'm having a *really* hard time envisioning a live use-case
for that.  Granting extension-creation ability requires a whole lot
more trust in the grantee than the ability to make new schemas
(which, in themselves, have about zero impact on anybody else).

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Stephen Frost <sfrost@snowman.net> writes:
> >> Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
> >>> Using GUCs to control some of this creates yet another place where
> >>> permission information is kept, and with it questions about how to get
> >>> to it, how to edit it, or to back it up and restore it, etc.  Also,
> >>> list-based parameters are particularly hard to manage by automated
> >>> tools.  I think we can do this within the existing permission system,
> >>> for example with pre-defined roles (for example, GRANT
> >>> pg_create_trusted_extension ...).  Also, CREATE EXTENSION should somehow
> >>> be controlled by the CREATE privilege on the containing database, so a
> >>> separate setting for database owner vs. regular user might not be
> >>> necessary.  Regular users would need both the role membership (given by
> >>> the overall superuser) and the privilege within the database (given by
> >>> the database owner).
>
> > Two things- first, this doesn't actually cover everything that the
> > proposed GUCs do- specifically, the proposed GUCs give you a way to
> > limit what specific extensions are allowed to be installed, and by whom.
> > Moving to a GRANT-based system removes the extension specificity and
> > leaves with just "is user X allowed to install extensions".
>
> True.  But do we care?  We did not have that flexibility before, either.

I'm not 100% sure that we do, but I wanted to mention it as a
difference.  Certainly there have previously been suggestions of having
a 'whitelist' similar to what you initially proposed, that are
extensions which non-superusers are allowed to install.

> I'd still keep the "trustable" property (probably renamed to "trusted"
> for simplicity) for extensions, so in the worst case, an admin could
> edit extension control files to add or remove the per-extension flag.

At a high level, I agree with the idea of an extension being able to be
marked as one that's "trusted" or not, but we would also need to come up
with exactly what that means for it to really have value, and I don't
think we've really done that yet.

> > Second,
> > this approach is requiring that a user who is allowed to create
> > extensions must also be allowed to create schemas on the database in
> > question.
>
> That doesn't seem like a big objection from here.  We could fix it
> by making a separate privilege bit, but I doubt that it's worth using
> up one of our limited set of spare bits for.

I do not agree that we should just shift to using default roles instead
of adding new options to GRANT because of an entirely internal
implementation detail that we could fix (and should, as I've said for
probably 10 years now...).

> >> I agree with the idea of requiring a DB-level privilege as well as
> >> the overall role.  Is it okay to re-use the CREATE privilege (which
> >> today only allows for CREATE SCHEMA), or do we need another one?
>
> > If we just created another one, wouldn't that remove the need to have a
> > database role?
>
> No, because then being DB owner would be alone be enough to let you
> install extensions (since as owner, you could certainly grant yourself
> all privileges on the DB, even if this were somehow not the default).
> We'd have to mangle GRANT's behavior to avoid that, and I don't think
> we should.  Nor do I think that DB ownership ought to be enough
> privilege by itself.

Really?  Why do you think that DB ownership shouldn't be enough for
this, for trusted extensions?

I agree that we don't want to mangle GRANT's behavior, at all, for this.

> >> I think re-using CREATE is probably all right, since it would only be
> >> useful for this purpose to users who also have "pg_install_extensions".
>
> > With this, you couldn't have a user who is able to create extensions but
> > not able to create schemas though.  That kind of combining of privileges
> > together really goes against the general principle of 'least privilege',
> > unless the action associated with one necessairly requires the other,
> > but I don't believe that's the case here.
>
> A point here is that many extensions involve creating their own schemas
> anyway.  Also, the ability to "relocate" an extension to a different
> schema is pretty meaningless if you can't create a schema to put it in.

What extensions require creating their own schema?  Every single
extension that's in contrib can be installed into the public schema
(concurrently, even) except for two hacks- plpgsql and adminpack, and
those go into pg_catalog for historical reasons more than anything else.

Creating a schema is an option for extensions but it isn't a
requirement.  I agree that you need the ability to create schemas if you
want to relocate one, but that's like needing SELECT to do an UPDATE
without a WHERE clause.  I also don't know that extension relocation is
really something that's commonly done.

> If I thought that there were a use-case for letting someone create
> extensions but not schemas, I'd be more eager to invent a new bit.
> But I'm having a *really* hard time envisioning a live use-case
> for that.  Granting extension-creation ability requires a whole lot
> more trust in the grantee than the ability to make new schemas
> (which, in themselves, have about zero impact on anybody else).

The idea of 'least privilege' isn't "well, I'm gonna grant you this
other thing that you don't actually need, just because I trust you with
this privilege that you do need."

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Chapman Flack
Date:
On 11/7/19 2:13 PM, Stephen Frost wrote:

>> That doesn't seem like a big objection from here.  We could fix it
>> by making a separate privilege bit, but I doubt that it's worth using
>> up one of our limited set of spare bits for.
> 
> I do not agree that we should just shift to using default roles instead
> of adding new options to GRANT because of an entirely internal

Am I mis-following the conversation in some way? I'm having trouble
seeing this as a question about a privilege bit, because that leads
straight on to the question of what database object carries the acl
item that grants that bit to a role. An extension isn't yet a database
object until after you create it.

So isn't this more a proposal to add another boolean attribute
to pg_authid, along the lines of rolcreatedb or rolbypassrls?


On the other hand, maybe thinking of it as a privilege bit could
lead somewhere interesting. A not-yet-installed extension isn't
a real database object, but it does have a synthesized existence
as a row in the pg_available_extensions view. Maybe that could
have an acl column, where a privilege (why not just CREATE?) could
be granted to one or more roles. Synthesizing that could rely on
some directive in the control file, or in some separate
extension_creators.conf file that would associate extensions with
roles.

That would avoid using a new bit, avoid adding a pg_authid attribute,
and avoid setting in stone a particular predefined role or two or
a single final meaning of 'trusted'. A site could create a few roles
and edit extension_creators.conf to associate extensions with them.

Maybe that's just a more ad-hoc and GUCless way of circling back
to what the original proposal would be doing with GUCs....

Regards,
-Chap



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Stephen Frost <sfrost@snowman.net> writes:
> * Tom Lane (tgl@sss.pgh.pa.us) wrote:
>> Stephen Frost <sfrost@snowman.net> writes:
>>> Two things- first, this doesn't actually cover everything that the
>>> proposed GUCs do- specifically, the proposed GUCs give you a way to
>>> limit what specific extensions are allowed to be installed, and by whom.
>>> Moving to a GRANT-based system removes the extension specificity and
>>> leaves with just "is user X allowed to install extensions".

>> True.  But do we care?  We did not have that flexibility before, either.

> I'm not 100% sure that we do, but I wanted to mention it as a
> difference.  Certainly there have previously been suggestions of having
> a 'whitelist' similar to what you initially proposed, that are
> extensions which non-superusers are allowed to install.

Right, but I'm not sure that we need multiple layers of that.  Flags
in the extension control files are a clear and understandable mechanism
for that.  I didn't especially like the idea of a GUC-based whitelist
even when I proposed it, and Peter's points against it are compelling
too, so I don't really want to go down that path anymore.  Do you have
another mechanism in mind?

> At a high level, I agree with the idea of an extension being able to be
> marked as one that's "trusted" or not, but we would also need to come up
> with exactly what that means for it to really have value, and I don't
> think we've really done that yet.

Agreed, we'd need to have a policy for what we'd mark.  The policy that
I more or less had in mind was to mark a contrib module as trusted if it
does not provide a mechanism for privilege escalation (such as access to
the filesystem, in the case of adminpack).  Some people might feel that
"contrib module X shouldn't be trusted because I'm not convinced it hasn't
got bugs", but I fear if we start trying to make decisions on that basis,
we'll be spending a whole lot of time arguing hypotheticals.

>> That doesn't seem like a big objection from here.  We could fix it
>> by making a separate privilege bit, but I doubt that it's worth using
>> up one of our limited set of spare bits for.

> I do not agree that we should just shift to using default roles instead
> of adding new options to GRANT because of an entirely internal
> implementation detail that we could fix (and should, as I've said for
> probably 10 years now...).

The default role is not a substitute for the GRANT bit, in my view of
this.  I think that what we're saying with that, or at least what
Peter evidently had in mind, is that we want extension installers to
have *both* privileges from the superuser and privileges from the
specific DB's owner.  We can manage the latter with GRANT, but not the
former.

It's certainly arguable that requiring a superuser-granted role is
enough privilege and we shouldn't bother with having a per-DB
restriction capability.  I'd be more inclined to go that route than
to add the overhead of a brand new ACL bit.

> Really?  Why do you think that DB ownership shouldn't be enough for
> this, for trusted extensions?

DB owners have never been particularly highly privileged in the past.
I think that suddenly saying they can install extensions is moving
the understanding of that privilege level quite a bit.  Although
admittedly, the precedent of trusted PLs would point to allowing them
to install trusted extensions without further ado.  So maybe a
different take on this is "allow installing trusted extensions if you
are DB owner *or* have the pg_install_trusted_extensions role"?

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Chapman Flack <chap@anastigmatix.net> writes:
> So isn't this more a proposal to add another boolean attribute
> to pg_authid, along the lines of rolcreatedb or rolbypassrls?

I think we've mostly concluded that default roles are superior
to pg_authid attributes.  The latter are legacy things rather
than a model to keep extending.

> On the other hand, maybe thinking of it as a privilege bit could
> lead somewhere interesting. A not-yet-installed extension isn't
> a real database object, but it does have a synthesized existence
> as a row in the pg_available_extensions view. Maybe that could
> have an acl column, where a privilege (why not just CREATE?) could
> be granted to one or more roles. Synthesizing that could rely on
> some directive in the control file, or in some separate
> extension_creators.conf file that would associate extensions with
> roles.

Meh ... that seems like building a whole new set of infrastructure
to solve something that we already have a couple of good models
for (i.e., default roles and object-based permissions).  I really
doubt it's worth the trouble to do that.

Although upthread I mentioned the possibility of a database admin
editing extension control files, I think most people would consider
that to be a truly last resort; you generally want those files to
remain as-distributed.  The alternative of a new config file is
slightly less unmaintainable, but only slightly.  There'd be no
way to update it from inside the database, short of writing a lot
of new infrastructure comparable to ALTER SYSTEM, and surely we
don't want to do that.

> Maybe that's just a more ad-hoc and GUCless way of circling back
> to what the original proposal would be doing with GUCs....

Yeah, I think if we really need per-extension configurability
of this, we're going to end up with a GUC.  It's just not worth
the trouble to build another mechanism that would support such a
need.  But I'm currently taking the position that we don't need
to support that.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
I wrote:
> Stephen Frost <sfrost@snowman.net> writes:
>> Really?  Why do you think that DB ownership shouldn't be enough for
>> this, for trusted extensions?

> DB owners have never been particularly highly privileged in the past.
> I think that suddenly saying they can install extensions is moving
> the understanding of that privilege level quite a bit.  Although
> admittedly, the precedent of trusted PLs would point to allowing them
> to install trusted extensions without further ado.  So maybe a
> different take on this is "allow installing trusted extensions if you
> are DB owner *or* have the pg_install_trusted_extensions role"?

After sleeping on it, I'm liking that idea; it's simple, and it
preserves the existing behavior that DB owners can install trusted PLs
without any extra permissions.  Now, if we follow this up by marking
most of contrib as trusted, we'd be expanding that existing privilege.
But I think that's all right: I don't recall anybody ever complaining
that they wanted to prevent DB owners from installing trusted PLs, and
I do recall people wishing that it didn't take superuser to install
the other stuff.

Accordingly, here's a patchset that does it like that.

I decided after looking at the existing default role names that
"pg_install_trusted_extension" (no plural) was more consistent
with the existing names than adding an "s".  I don't find that
precedent particularly charming, but it's what we've got.

I also renamed the extension property from "trustable" to "trusted".
There are at least a couple of reasons to be dissatisfied with that:

(1) There's potential for confusion between the notion of a trusted
extension and that of a trusted PL; those properties do roughly
similar things, but they're not exactly equivalent.  I didn't think
this was enough of a problem to justify choosing a different name,
but somebody else might think differently.

(2) If we were starting this design from scratch, we'd probably not
have two interrelated boolean properties "superuser" and "trusted",
but one three-state enum property.  The enum approach would likely
be a lot easier to extend if we eventually grow more privilege
levels for extension installation.  I'm not sure whether it's worth
breaking backwards compatibility now to keep our options open for
that, though.  We could preserve extension control file
compatibility easily enough by keeping "superuser = true" and
"superuser = false" as allowed legacy spellings for two values of
an enum property.  But the pg_available_extension_versions view is
a tougher nut.  On the other hand, maybe replacing its "superuser"
column with something else wouldn't really cause many problems.

Other than getting rid of the GUCs in favor of this design,
it's mostly the same patchset as before.  0003 and 0004 haven't
changed at all, and 0002 only differs by adjusting the test case.

            regards, tom lane

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 55694c4..a573dfb 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8514,7 +8514,15 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
      <row>
       <entry><structfield>superuser</structfield></entry>
       <entry><type>bool</type></entry>
-      <entry>True if only superusers are allowed to install this extension</entry>
+      <entry>True if only superusers are allowed to install this extension
+       (but see <structfield>trusted</structfield>)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>trusted</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry>True if the extension can be installed by non-superusers
+       with appropriate privileges</entry>
      </row>

      <row>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f2..e2807d0 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -576,6 +576,32 @@
         version.  If it is set to <literal>false</literal>, just the privileges
         required to execute the commands in the installation or update script
         are required.
+        This should normally be set to <literal>true</literal> if any of the
+        script commands require superuser privileges.  (Such commands would
+        fail anyway, but it's more user-friendly to give the error up front.)
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>trusted</varname> (<type>boolean</type>)</term>
+      <listitem>
+       <para>
+        This parameter, if set to <literal>true</literal> (which is not the
+        default), allows some non-superusers to install an extension that
+        has <varname>superuser</varname> set to <literal>true</literal>.
+        Specifically, installation will be permitted for the owner of the
+        current database, and for anyone who has been granted
+        the <literal>pg_install_trusted_extension</literal> role.
+        When the user executing <command>CREATE EXTENSION</command> is not
+        a superuser but is allowed to install by virtue of this parameter,
+        then the installation or update script is run as the bootstrap
+        superuser, not as the calling user.
+        This parameter is irrelevant if <varname>superuser</varname> is
+        <literal>false</literal>.
+        Generally, this should not be set true for extensions that could
+        allow access to otherwise-superuser-only abilities, such as
+        filesystem access.
        </para>
       </listitem>
      </varlistentry>
@@ -642,6 +668,18 @@
     </para>

     <para>
+     If the extension script contains the
+     string <literal>@extowner@</literal>, that string is replaced with the
+     (suitably quoted) name of the user calling <command>CREATE
+     EXTENSION</command> or <command>ALTER EXTENSION</command>.  Typically
+     this feature is used by extensions that are marked trusted to assign
+     ownership of selected objects to the calling user rather than the
+     bootstrap superuser.  (One should be careful about doing so, however.
+     For example, assigning ownership of a C-language function to a
+     non-superuser would create a privilege escalation path for that user.)
+    </para>
+
+    <para>
      While the script files can contain any characters allowed by the specified
      encoding, control files should contain only plain ASCII, because there
      is no way for <productname>PostgreSQL</productname> to know what encoding a
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index 36837f9..7cd4346 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -47,14 +47,26 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
   </para>

   <para>
-   Loading an extension requires the same privileges that would be
-   required to create its component objects.  For most extensions this
-   means superuser or database owner privileges are needed.
    The user who runs <command>CREATE EXTENSION</command> becomes the
    owner of the extension for purposes of later privilege checks, as well
    as the owner of any objects created by the extension's script.
   </para>

+  <para>
+   Loading an extension ordinarily requires the same privileges that would
+   be required to create its component objects.  For many extensions this
+   means superuser privileges are needed.
+   However, if the extension is marked <firstterm>trusted</firstterm> in
+   its control file, then it can be installed by a non-superuser who has
+   suitable privileges (that is, owns the current database or has been
+   granted the <literal>pg_install_trusted_extension</literal> role).  In
+   this case the extension object itself will be owned by the calling user,
+   but the contained objects will be owned by the bootstrap superuser
+   (unless the extension's script explicitly assigns them to the calling
+   user).  This configuration gives the calling user the right to drop the
+   extension, but not to modify individual objects within it.
+  </para>
+
  </refsect1>

  <refsect1>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 66f1627..90f637f 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -556,6 +556,10 @@ DROP ROLE doomed_role;
        <entry>Allow executing programs on the database server as the user the database runs as with
        COPY and other functions which allow executing a server-side program.</entry>
       </row>
+      <row>
+       <entry>pg_install_trusted_extension</entry>
+       <entry>Allow installation of <quote>trusted</quote> extensions.</entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
@@ -589,6 +593,17 @@ DROP ROLE doomed_role;
   </para>

   <para>
+  The <literal>pg_install_trusted_extension</literal> role allows grantees
+  to install <link linkend="extend-extensions">extensions</link> that are
+  marked <quote>trusted</quote> in their control files.  This is a
+  privilege that is available automatically to database owners, but
+  granting this role allows administrators to let other non-superuser roles
+  do it too.  Generally, unless the <quote>trusted</quote> marking has been
+  applied to extensions incautiously, granting this role carries no large
+  security risk.
+  </para>
+
+  <para>
   Care should be taken when granting these roles to ensure they are only used where
   needed and with the understanding that these roles grant access to privileged
   information.
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 9fe4a47..6560605 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -317,7 +317,8 @@ CREATE VIEW pg_available_extensions AS

 CREATE VIEW pg_available_extension_versions AS
     SELECT E.name, E.version, (X.extname IS NOT NULL) AS installed,
-           E.superuser, E.relocatable, E.schema, E.requires, E.comment
+           E.superuser, E.trusted, E.relocatable,
+           E.schema, E.requires, E.comment
       FROM pg_available_extension_versions() AS E
            LEFT JOIN pg_extension AS X
              ON E.name = X.extname AND E.version = X.extversion;
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index a04b0c9..bf66a1c 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -40,6 +40,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
@@ -84,6 +85,7 @@ typedef struct ExtensionControlFile
     char       *schema;            /* target schema (allowed if !relocatable) */
     bool        relocatable;    /* is ALTER EXTENSION SET SCHEMA supported? */
     bool        superuser;        /* must be superuser to install? */
+    bool        trusted;        /* allow becoming superuser on the fly? */
     int            encoding;        /* encoding of the script file, or -1 */
     List       *requires;        /* names of prerequisite extensions */
 } ExtensionControlFile;
@@ -558,6 +560,14 @@ parse_extension_control_file(ExtensionControlFile *control,
                          errmsg("parameter \"%s\" requires a Boolean value",
                                 item->name)));
         }
+        else if (strcmp(item->name, "trusted") == 0)
+        {
+            if (!parse_bool(item->value, &control->trusted))
+                ereport(ERROR,
+                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                         errmsg("parameter \"%s\" requires a Boolean value",
+                                item->name)));
+        }
         else if (strcmp(item->name, "encoding") == 0)
         {
             control->encoding = pg_valid_server_encoding(item->value);
@@ -614,6 +624,7 @@ read_extension_control_file(const char *extname)
     control->name = pstrdup(extname);
     control->relocatable = false;
     control->superuser = true;
+    control->trusted = false;
     control->encoding = -1;

     /*
@@ -795,6 +806,27 @@ execute_sql_string(const char *sql)
 }

 /*
+ * Policy function: is the given extension trusted for installation by a
+ * non-superuser?
+ *
+ * (Update the errhint logic below if you change this.)
+ */
+static bool
+extension_is_trusted(ExtensionControlFile *control)
+{
+    /* Never trust unless extension's control file says it's okay */
+    if (!control->trusted)
+        return false;
+    /* Database owner can install */
+    if (pg_database_ownercheck(MyDatabaseId, GetUserId()))
+        return true;
+    /* Members of pg_install_trusted_extension can install */
+    if (has_privs_of_role(GetUserId(), DEFAULT_ROLE_INSTALL_TRUSTED_EXTENSION))
+        return true;
+    return false;
+}
+
+/*
  * Execute the appropriate script file for installing or updating the extension
  *
  * If from_version isn't NULL, it's an update
@@ -806,35 +838,56 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                          List *requiredSchemas,
                          const char *schemaName, Oid schemaOid)
 {
+    bool        switch_to_superuser = false;
     char       *filename;
+    Oid            save_userid = 0;
+    int            save_sec_context = 0;
     int            save_nestlevel;
     StringInfoData pathbuf;
     ListCell   *lc;

     /*
-     * Enforce superuser-ness if appropriate.  We postpone this check until
-     * here so that the flag is correctly associated with the right script(s)
-     * if it's set in secondary control files.
+     * Enforce superuser-ness if appropriate.  We postpone these checks until
+     * here so that the control flags are correctly associated with the right
+     * script(s) if they happen to be set in secondary control files.
      */
     if (control->superuser && !superuser())
     {
-        if (from_version == NULL)
+        if (extension_is_trusted(control))
+            switch_to_superuser = true;
+        else if (from_version == NULL)
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("permission denied to create extension \"%s\"",
                             control->name),
-                     errhint("Must be superuser to create this extension.")));
+                     control->trusted
+                     ? errhint("Must be database owner or member of pg_install_trusted_extension to create this
extension.")
+                     : errhint("Must be superuser to create this extension.")));
         else
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("permission denied to update extension \"%s\"",
                             control->name),
-                     errhint("Must be superuser to update this extension.")));
+                     control->trusted
+                     ? errhint("Must be database owner or member of pg_install_trusted_extension to update this
extension.")
+                     : errhint("Must be superuser to update this extension.")));
     }

     filename = get_extension_script_filename(control, from_version, version);

     /*
+     * If installing a trusted extension on behalf of a non-superuser, become
+     * the bootstrap superuser.  (This switch will be cleaned up automatically
+     * if the transaction aborts, as will the GUC changes below.)
+     */
+    if (switch_to_superuser)
+    {
+        GetUserIdAndSecContext(&save_userid, &save_sec_context);
+        SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
+                               save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+    }
+
+    /*
      * 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.
@@ -907,6 +960,22 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                                         CStringGetTextDatum("ng"));

         /*
+         * If the script uses @extowner@, substitute the calling username.
+         */
+        if (strstr(c_sql, "@extowner@"))
+        {
+            Oid            uid = switch_to_superuser ? save_userid : GetUserId();
+            const char *userName = GetUserNameFromId(uid, false);
+            const char *qUserName = quote_identifier(userName);
+
+            t_sql = DirectFunctionCall3Coll(replace_text,
+                                            C_COLLATION_OID,
+                                            t_sql,
+                                            CStringGetTextDatum("@extowner@"),
+                                            CStringGetTextDatum(qUserName));
+        }
+
+        /*
          * If it's not relocatable, substitute the target schema name for
          * occurrences of @extschema@.
          *
@@ -953,6 +1022,12 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
      * Restore the GUC variables we set above.
      */
     AtEOXact_GUC(true, save_nestlevel);
+
+    /*
+     * Restore authentication state if needed.
+     */
+    if (switch_to_superuser)
+        SetUserIdAndSecContext(save_userid, save_sec_context);
 }

 /*
@@ -2113,8 +2188,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
     {
         ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
         ExtensionControlFile *control;
-        Datum        values[7];
-        bool        nulls[7];
+        Datum        values[8];
+        bool        nulls[8];
         ListCell   *lc2;

         if (!evi->installable)
@@ -2135,24 +2210,26 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
         values[1] = CStringGetTextDatum(evi->name);
         /* superuser */
         values[2] = BoolGetDatum(control->superuser);
+        /* trusted */
+        values[3] = BoolGetDatum(control->trusted);
         /* relocatable */
-        values[3] = BoolGetDatum(control->relocatable);
+        values[4] = BoolGetDatum(control->relocatable);
         /* schema */
         if (control->schema == NULL)
-            nulls[4] = true;
+            nulls[5] = true;
         else
-            values[4] = DirectFunctionCall1(namein,
+            values[5] = DirectFunctionCall1(namein,
                                             CStringGetDatum(control->schema));
         /* requires */
         if (control->requires == NIL)
-            nulls[5] = true;
+            nulls[6] = true;
         else
-            values[5] = convert_requires_to_datum(control->requires);
+            values[6] = convert_requires_to_datum(control->requires);
         /* comment */
         if (control->comment == NULL)
-            nulls[6] = true;
+            nulls[7] = true;
         else
-            values[6] = CStringGetTextDatum(control->comment);
+            values[7] = CStringGetTextDatum(control->comment);

         tuplestore_putvalues(tupstore, tupdesc, values, nulls);

@@ -2180,16 +2257,18 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
                 values[1] = CStringGetTextDatum(evi2->name);
                 /* superuser */
                 values[2] = BoolGetDatum(control->superuser);
+                /* trusted */
+                values[3] = BoolGetDatum(control->trusted);
                 /* relocatable */
-                values[3] = BoolGetDatum(control->relocatable);
+                values[4] = BoolGetDatum(control->relocatable);
                 /* schema stays the same */
                 /* requires */
                 if (control->requires == NIL)
-                    nulls[5] = true;
+                    nulls[6] = true;
                 else
                 {
-                    values[5] = convert_requires_to_datum(control->requires);
-                    nulls[5] = false;
+                    values[6] = convert_requires_to_datum(control->requires);
+                    nulls[6] = false;
                 }
                 /* comment stays the same */

diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index c21f97a..3b04259 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -60,5 +60,10 @@
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '9495', oid_symbol => 'DEFAULT_ROLE_INSTALL_TRUSTED_EXTENSION',
+  rolname => 'pg_install_trusted_extension', rolsuper => 'f', rolinherit => 't',
+  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+  rolpassword => '_null_', rolvaliduntil => '_null_' },

 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 58ea5b9..b50eefb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9459,9 +9459,9 @@
   proname => 'pg_available_extension_versions', procost => '10',
   prorows => '100', proretset => 't', provolatile => 's',
   prorettype => 'record', proargtypes => '',
-  proallargtypes => '{name,text,bool,bool,name,_name,text}',
-  proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,version,superuser,relocatable,schema,requires,comment}',
+  proallargtypes => '{name,text,bool,bool,bool,name,_name,text}',
+  proargmodes => '{o,o,o,o,o,o,o,o}',
+  proargnames => '{name,version,superuser,trusted,relocatable,schema,requires,comment}',
   prosrc => 'pg_available_extension_versions' },
 { oid => '3084', descr => 'list an extension\'s version update paths',
   proname => 'pg_extension_update_paths', procost => '10', prorows => '100',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 210e9cd..88f63ed 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1311,11 +1311,12 @@ pg_available_extension_versions| SELECT e.name,
     e.version,
     (x.extname IS NOT NULL) AS installed,
     e.superuser,
+    e.trusted,
     e.relocatable,
     e.schema,
     e.requires,
     e.comment
-   FROM (pg_available_extension_versions() e(name, version, superuser, relocatable, schema, requires, comment)
+   FROM (pg_available_extension_versions() e(name, version, superuser, trusted, relocatable, schema, requires,
comment)
      LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion))));
 pg_available_extensions| SELECT e.name,
     e.default_version,
diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile
index 9b1c514..e4d0a0b 100644
--- a/src/pl/plperl/GNUmakefile
+++ b/src/pl/plperl/GNUmakefile
@@ -55,8 +55,10 @@ endif # win32

 SHLIB_LINK = $(perl_embed_ldflags)

-REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-extension=plperl  --load-extension=plperlu
-REGRESS = plperl plperl_lc plperl_trigger plperl_shared plperl_elog plperl_util plperl_init plperlu plperl_array
plperl_callplperl_transaction 
+REGRESS_OPTS = --dbname=$(PL_TESTDB)
+REGRESS = plperl_setup plperl plperl_lc plperl_trigger plperl_shared \
+    plperl_elog plperl_util plperl_init plperlu plperl_array \
+    plperl_call plperl_transaction
 # if Perl can support two interpreters in one backend,
 # test plperl-and-plperlu cases
 ifneq ($(PERL),)
diff --git a/src/pl/plperl/expected/plperl_setup.out b/src/pl/plperl/expected/plperl_setup.out
new file mode 100644
index 0000000..7cb1f25
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_setup.out
@@ -0,0 +1,60 @@
+--
+-- Install the plperl and plperlu extensions
+--
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- suitable permissions have been granted.
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;  -- fail
+ERROR:  permission denied to create extension "plperl"
+HINT:  Must be database owner or member of pg_install_trusted_extension to create this extension.
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+RESET ROLE;
+GRANT pg_install_trusted_extension TO regress_user1;
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+ foo1
+------
+    1
+(1 row)
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+ERROR:  permission denied for language plperl
+SET ROLE regress_user1;
+grant usage on language plperl to regress_user2;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+ foo2
+------
+    2
+(1 row)
+
+SET ROLE regress_user1;
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+ERROR:  cannot drop language plperl because extension plperl requires it
+HINT:  You can drop extension plperl instead.
+DROP EXTENSION plperl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to function foo1()
+drop cascades to function foo2()
+-- Clean up
+RESET ROLE;
+DROP USER regress_user1;
+DROP USER regress_user2;
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plperl/plperl--1.0.sql b/src/pl/plperl/plperl--1.0.sql
index f716ba1..5ff31e7 100644
--- a/src/pl/plperl/plperl--1.0.sql
+++ b/src/pl/plperl/plperl--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plperl/plperl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperl;
+CREATE FUNCTION plperl_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperl_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plperl
+  HANDLER plperl_call_handler
+  INLINE plperl_inline_handler
+  VALIDATOR plperl_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plperl OWNER TO @extowner@;

 COMMENT ON LANGUAGE plperl IS 'PL/Perl procedural language';
diff --git a/src/pl/plperl/plperl.control b/src/pl/plperl/plperl.control
index 6faace1..3a2230a 100644
--- a/src/pl/plperl/plperl.control
+++ b/src/pl/plperl/plperl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plperl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/plperl/plperlu--1.0.sql b/src/pl/plperl/plperlu--1.0.sql
index 7efb4fb..10d7594 100644
--- a/src/pl/plperl/plperlu--1.0.sql
+++ b/src/pl/plperl/plperlu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plperl/plperlu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperlu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperlu;
+CREATE FUNCTION plperlu_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperlu_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plperlu
+  HANDLER plperlu_call_handler
+  INLINE plperlu_inline_handler
+  VALIDATOR plperlu_validator;

 COMMENT ON LANGUAGE plperlu IS 'PL/PerlU untrusted procedural language';
diff --git a/src/pl/plperl/sql/plperl_setup.sql b/src/pl/plperl/sql/plperl_setup.sql
new file mode 100644
index 0000000..9c7b52c
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_setup.sql
@@ -0,0 +1,58 @@
+--
+-- Install the plperl and plperlu extensions
+--
+
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- suitable permissions have been granted.
+
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;  -- fail
+CREATE EXTENSION plperlu;  -- fail
+
+RESET ROLE;
+
+GRANT pg_install_trusted_extension TO regress_user1;
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+
+SET ROLE regress_user1;
+
+grant usage on language plperl to regress_user2;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+
+SET ROLE regress_user1;
+
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+DROP EXTENSION plperl CASCADE;
+
+-- Clean up
+RESET ROLE;
+DROP USER regress_user1;
+DROP USER regress_user2;
+
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plpgsql/src/plpgsql--1.0.sql b/src/pl/plpgsql/src/plpgsql--1.0.sql
index ab6fa84..6e5b990 100644
--- a/src/pl/plpgsql/src/plpgsql--1.0.sql
+++ b/src/pl/plpgsql/src/plpgsql--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plpgsql/src/plpgsql--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpgsql_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpgsql;
+CREATE FUNCTION plpgsql_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpgsql_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plpgsql
+  HANDLER plpgsql_call_handler
+  INLINE plpgsql_inline_handler
+  VALIDATOR plpgsql_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plpgsql OWNER TO @extowner@;

 COMMENT ON LANGUAGE plpgsql IS 'PL/pgSQL procedural language';
diff --git a/src/pl/plpgsql/src/plpgsql.control b/src/pl/plpgsql/src/plpgsql.control
index b320227..42e764b 100644
--- a/src/pl/plpgsql/src/plpgsql.control
+++ b/src/pl/plpgsql/src/plpgsql.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plpgsql'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/plpython/plpython2u--1.0.sql b/src/pl/plpython/plpython2u--1.0.sql
index 661cc66..69f7477 100644
--- a/src/pl/plpython/plpython2u--1.0.sql
+++ b/src/pl/plpython/plpython2u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython2u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython2_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython2u;
+CREATE FUNCTION plpython2_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython2_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython2u
+  HANDLER plpython2_call_handler
+  INLINE plpython2_inline_handler
+  VALIDATOR plpython2_validator;

 COMMENT ON LANGUAGE plpython2u IS 'PL/Python2U untrusted procedural language';
diff --git a/src/pl/plpython/plpython3u--1.0.sql b/src/pl/plpython/plpython3u--1.0.sql
index c0d6ea8..ba2e6ac 100644
--- a/src/pl/plpython/plpython3u--1.0.sql
+++ b/src/pl/plpython/plpython3u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython3u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython3_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython3u;
+CREATE FUNCTION plpython3_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython3_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython3u
+  HANDLER plpython3_call_handler
+  INLINE plpython3_inline_handler
+  VALIDATOR plpython3_validator;

 COMMENT ON LANGUAGE plpython3u IS 'PL/Python3U untrusted procedural language';
diff --git a/src/pl/plpython/plpythonu--1.0.sql b/src/pl/plpython/plpythonu--1.0.sql
index 4a3e64a..4c6f7c3 100644
--- a/src/pl/plpython/plpythonu--1.0.sql
+++ b/src/pl/plpython/plpythonu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpythonu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpythonu;
+CREATE FUNCTION plpython_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpythonu
+  HANDLER plpython_call_handler
+  INLINE plpython_inline_handler
+  VALIDATOR plpython_validator;

 COMMENT ON LANGUAGE plpythonu IS 'PL/PythonU untrusted procedural language';
diff --git a/src/pl/tcl/pltcl--1.0.sql b/src/pl/tcl/pltcl--1.0.sql
index 34a68c8..2ed2b92 100644
--- a/src/pl/tcl/pltcl--1.0.sql
+++ b/src/pl/tcl/pltcl--1.0.sql
@@ -1,11 +1,12 @@
 /* src/pl/tcl/pltcl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltcl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltcl;
+CREATE TRUSTED LANGUAGE pltcl
+  HANDLER pltcl_call_handler;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE pltcl OWNER TO @extowner@;

 COMMENT ON LANGUAGE pltcl IS 'PL/Tcl procedural language';
diff --git a/src/pl/tcl/pltcl.control b/src/pl/tcl/pltcl.control
index b9dc1b8..1568c17 100644
--- a/src/pl/tcl/pltcl.control
+++ b/src/pl/tcl/pltcl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/pltcl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/tcl/pltclu--1.0.sql b/src/pl/tcl/pltclu--1.0.sql
index e05b470..fca869f 100644
--- a/src/pl/tcl/pltclu--1.0.sql
+++ b/src/pl/tcl/pltclu--1.0.sql
@@ -1,11 +1,9 @@
 /* src/pl/tcl/pltclu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltclu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltclu;
+CREATE LANGUAGE pltclu
+  HANDLER pltclu_call_handler;

 COMMENT ON LANGUAGE pltclu IS 'PL/TclU untrusted procedural language';
diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
index 13b28b1..2a9c7e8 100644
--- a/doc/src/sgml/ref/create_language.sgml
+++ b/doc/src/sgml/ref/create_language.sgml
@@ -21,9 +21,9 @@ PostgreSQL documentation

  <refsynopsisdiv>
 <synopsis>
-CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable
class="parameter">inline_handler</replaceable>] [ VALIDATOR <replaceable>valfunction</replaceable> ] 
+CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 </synopsis>
  </refsynopsisdiv>

@@ -37,21 +37,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
    defined in this new language.
   </para>

-  <note>
-   <para>
-    As of <productname>PostgreSQL</productname> 9.1, most procedural
-    languages have been made into <quote>extensions</quote>, and should
-    therefore be installed with <xref linkend="sql-createextension"/>
-    not <command>CREATE LANGUAGE</command>.  Direct use of
-    <command>CREATE LANGUAGE</command> should now be confined to
-    extension installation scripts.  If you have a <quote>bare</quote>
-    language in your database, perhaps as a result of an upgrade,
-    you can convert it to an extension using
-    <literal>CREATE EXTENSION <replaceable>langname</replaceable> FROM
-    unpackaged</literal>.
-   </para>
-  </note>
-
   <para>
    <command>CREATE LANGUAGE</command> effectively associates the
    language name with handler function(s) that are responsible for executing
@@ -60,53 +45,32 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   There are two forms of the <command>CREATE LANGUAGE</command> command.
-   In the first form, the user supplies just the name of the desired
-   language, and the <productname>PostgreSQL</productname> server consults
-   the <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
-   system catalog to determine the correct parameters.  In the second form,
-   the user supplies the language parameters along with the language name.
-   The second form can be used to create a language that is not defined in
-   <structname>pg_pltemplate</structname>, but this approach is considered obsolescent.
-  </para>
-
-  <para>
-   When the server finds an entry in the <structname>pg_pltemplate</structname> catalog
-   for the given language name, it will use the catalog data even if the
-   command includes language parameters.  This behavior simplifies loading of
-   old dump files, which are likely to contain out-of-date information
-   about language support functions.
-  </para>
-
-  <para>
-   Ordinarily, the user must have the
-   <productname>PostgreSQL</productname> superuser privilege to
-   register a new language.  However, the owner of a database can register
-   a new language within that database if the language is listed in
-   the <structname>pg_pltemplate</structname> catalog and is marked
-   as allowed to be created by database owners (<structfield>tmpldbacreate</structfield>
-   is true).  The default is that trusted languages can be created
-   by database owners, but this can be adjusted by superusers by modifying
-   the contents of <structname>pg_pltemplate</structname>.
-   The creator of a language becomes its owner and can later
-   drop it, rename it, or assign it to a new owner.
+   The form of <command>CREATE LANGUAGE</command> that does not supply
+   any handler function is obsolete.  For backwards compatibility with
+   old dump files, it is interpreted as <command>CREATE EXTENSION</command>.
+   That will work if the language has been packaged into an extension of
+   the same name, which is the conventional way to set up procedural
+   languages.
   </para>

   <para>
    <command>CREATE OR REPLACE LANGUAGE</command> will either create a
    new language, or replace an existing definition.  If the language
-   already exists, its parameters are updated according to the values
-   specified or taken from <structname>pg_pltemplate</structname>,
+   already exists, its parameters are updated according to the command,
    but the language's ownership and permissions settings do not change,
    and any existing functions written in the language are assumed to still
-   be valid.  In addition to the normal privilege requirements for creating
-   a language, the user must be superuser or owner of the existing language.
-   The <literal>REPLACE</literal> case is mainly meant to be used to
-   ensure that the language exists.  If the language has a
-   <structname>pg_pltemplate</structname> entry then <literal>REPLACE</literal>
-   will not actually change anything about an existing definition, except in
-   the unusual case where the <structname>pg_pltemplate</structname> entry
-   has been modified since the language was created.
+   be valid.
+  </para>
+
+  <para>
+   One must have the
+   <productname>PostgreSQL</productname> superuser privilege to
+   register a new language or change an existing language's parameters.
+   However, once the language is created it is valid to assign ownership of
+   it to a non-superuser, who may then drop it, change its permissions,
+   rename it, or assign it to a new owner.  (Do not, however, assign
+   ownership of the underlying C functions to a non-superuser; that would
+   create a privilege escalation path for that user.)
   </para>
  </refsect1>

@@ -218,12 +182,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
      </listitem>
     </varlistentry>
    </variablelist>
-
-  <para>
-   The <literal>TRUSTED</literal> option and the support function name(s) are
-   ignored if the server has an entry for the specified language
-   name in <structname>pg_pltemplate</structname>.
-  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-notes">
@@ -255,18 +213,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   The call handler function, the inline handler function (if any),
-   and the validator function (if any)
-   must already exist if the server does not have an entry for the language
-   in <structname>pg_pltemplate</structname>.  But when there is an entry,
-   the functions need not already exist;
-   they will be automatically defined if not present in the database.
-   (This might result in <command>CREATE LANGUAGE</command> failing, if the
-   shared library that implements the language is not available in
-   the installation.)
-  </para>
-
-  <para>
    In <productname>PostgreSQL</productname> versions before 7.3, it was
    necessary to declare handler functions as returning the placeholder
    type <type>opaque</type>, rather than <type>language_handler</type>.
@@ -281,23 +227,20 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   <title>Examples</title>

   <para>
-   The preferred way of creating any of the standard procedural languages
-   is just:
-<programlisting>
-CREATE LANGUAGE plperl;
-</programlisting>
-  </para>
-
-  <para>
-   For a language not known in the <structname>pg_pltemplate</structname> catalog, a
-   sequence such as this is needed:
+   A minimal sequence for creating a new procedural language is:
 <programlisting>
 CREATE FUNCTION plsample_call_handler() RETURNS language_handler
     AS '$libdir/plsample'
     LANGUAGE C;
 CREATE LANGUAGE plsample
     HANDLER plsample_call_handler;
-</programlisting></para>
+</programlisting>
+   Typically that would be written in an extension's creation script,
+   and users would do this to install the extension:
+<programlisting>
+CREATE EXTENSION plsample;
+</programlisting>
+  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-compat">
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index bf66a1c..21e6dd7 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2279,6 +2279,64 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 }

 /*
+ * Test whether the given extension exists (not whether it's installed)
+ *
+ * This checks for the existence of a matching control file in the extension
+ * directory.  That's not a bulletproof check, since the file might be
+ * invalid, but this is only used for hints so it doesn't have to be 100%
+ * right.
+ */
+bool
+extension_file_exists(const char *extensionName)
+{
+    bool        result = false;
+    char       *location;
+    DIR           *dir;
+    struct dirent *de;
+
+    location = get_extension_control_directory();
+    dir = AllocateDir(location);
+
+    /*
+     * If the control directory doesn't exist, we want to silently return
+     * false.  Any other error will be reported by ReadDir.
+     */
+    if (dir == NULL && errno == ENOENT)
+    {
+        /* do nothing */
+    }
+    else
+    {
+        while ((de = ReadDir(dir, location)) != NULL)
+        {
+            char       *extname;
+
+            if (!is_extension_control_filename(de->d_name))
+                continue;
+
+            /* extract extension name from 'name.control' filename */
+            extname = pstrdup(de->d_name);
+            *strrchr(extname, '.') = '\0';
+
+            /* ignore it if it's an auxiliary control file */
+            if (strstr(extname, "--"))
+                continue;
+
+            /* done if it matches request */
+            if (strcmp(extname, extensionName) == 0)
+            {
+                result = true;
+                break;
+            }
+        }
+
+        FreeDir(dir);
+    }
+
+    return result;
+}
+
+/*
  * Convert a list of extension names to a name[] Datum
  */
 static Datum
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 40f1f9a..aa408b8 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/extension.h"
 #include "commands/proclang.h"
 #include "executor/execdesc.h"
 #include "executor/executor.h"
@@ -991,7 +992,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_OBJECT),
                  errmsg("language \"%s\" does not exist", language),
-                 (PLTemplateExists(language) ?
+                 (extension_file_exists(language) ?
                   errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

     languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
@@ -2225,7 +2226,7 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_OBJECT),
                  errmsg("language \"%s\" does not exist", language),
-                 (PLTemplateExists(language) ?
+                 (extension_file_exists(language) ?
                   errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

     languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index 343cd1d..520a603 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -13,329 +13,110 @@
  */
 #include "postgres.h"

-#include "access/genam.h"
-#include "access/htup_details.h"
 #include "access/table.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
-#include "catalog/pg_authid.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
-#include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/proclang.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
-#include "parser/parser.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
-#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"


-typedef struct
-{
-    bool        tmpltrusted;    /* trusted? */
-    bool        tmpldbacreate;    /* db owner allowed to create? */
-    char       *tmplhandler;    /* name of handler function */
-    char       *tmplinline;        /* name of anonymous-block handler, or NULL */
-    char       *tmplvalidator;    /* name of validator function, or NULL */
-    char       *tmpllibrary;    /* path of shared library */
-} PLTemplate;
-
-static ObjectAddress create_proc_lang(const char *languageName, bool replace,
-                                      Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                                      Oid valOid, bool trusted);
-static PLTemplate *find_language_template(const char *languageName);
-
 /*
  * CREATE LANGUAGE
  */
 ObjectAddress
 CreateProceduralLanguage(CreatePLangStmt *stmt)
 {
-    PLTemplate *pltemplate;
-    ObjectAddress tmpAddr;
+    const char *languageName = stmt->plname;
+    Oid            languageOwner = GetUserId();
     Oid            handlerOid,
                 inlineOid,
                 valOid;
     Oid            funcrettype;
     Oid            funcargtypes[1];
+    Relation    rel;
+    TupleDesc    tupDesc;
+    Datum        values[Natts_pg_language];
+    bool        nulls[Natts_pg_language];
+    bool        replaces[Natts_pg_language];
+    NameData    langname;
+    HeapTuple    oldtup;
+    HeapTuple    tup;
+    Oid            langoid;
+    bool        is_update;
+    ObjectAddress myself,
+                referenced;

     /*
-     * If we have template information for the language, ignore the supplied
-     * parameters (if any) and use the template information.
+     * Check permission
      */
-    if ((pltemplate = find_language_template(stmt->plname)) != NULL)
-    {
-        List       *funcname;
-
-        /*
-         * Give a notice if we are ignoring supplied parameters.
-         */
-        if (stmt->plhandler)
-            ereport(NOTICE,
-                    (errmsg("using pg_pltemplate information instead of CREATE LANGUAGE parameters")));
-
-        /*
-         * Check permission
-         */
-        if (!superuser())
-        {
-            if (!pltemplate->tmpldbacreate)
-                ereport(ERROR,
-                        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                         errmsg("must be superuser to create procedural language \"%s\"",
-                                stmt->plname)));
-            if (!pg_database_ownercheck(MyDatabaseId, GetUserId()))
-                aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
-                               get_database_name(MyDatabaseId));
-        }
-
-        /*
-         * Find or create the handler function, which we force to be in the
-         * pg_catalog schema.  If already present, it must have the correct
-         * return type.
-         */
-        funcname = SystemFuncName(pltemplate->tmplhandler);
-        handlerOid = LookupFuncName(funcname, 0, funcargtypes, true);
-        if (OidIsValid(handlerOid))
-        {
-            funcrettype = get_func_rettype(handlerOid);
-            if (funcrettype != LANGUAGE_HANDLEROID)
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(funcname), "language_handler")));
-        }
-        else
-        {
-            tmpAddr = ProcedureCreate(pltemplate->tmplhandler,
-                                      PG_CATALOG_NAMESPACE,
-                                      false,    /* replace */
-                                      false,    /* returnsSet */
-                                      LANGUAGE_HANDLEROID,
-                                      BOOTSTRAP_SUPERUSERID,
-                                      ClanguageId,
-                                      F_FMGR_C_VALIDATOR,
-                                      pltemplate->tmplhandler,
-                                      pltemplate->tmpllibrary,
-                                      PROKIND_FUNCTION,
-                                      false,    /* security_definer */
-                                      false,    /* isLeakProof */
-                                      false,    /* isStrict */
-                                      PROVOLATILE_VOLATILE,
-                                      PROPARALLEL_UNSAFE,
-                                      buildoidvector(funcargtypes, 0),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      NIL,
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      InvalidOid,
-                                      1,
-                                      0);
-            handlerOid = tmpAddr.objectId;
-        }
-
-        /*
-         * Likewise for the anonymous block handler, if required; but we don't
-         * care about its return type.
-         */
-        if (pltemplate->tmplinline)
-        {
-            funcname = SystemFuncName(pltemplate->tmplinline);
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(inlineOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplinline,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplinline,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                inlineOid = tmpAddr.objectId;
-            }
-        }
-        else
-            inlineOid = InvalidOid;
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 errmsg("must be superuser to create custom procedural language")));

+    /*
+     * Lookup the PL handler function and check that it is of the expected
+     * return type
+     */
+    Assert(stmt->plhandler);
+    handlerOid = LookupFuncName(stmt->plhandler, 0, funcargtypes, false);
+    funcrettype = get_func_rettype(handlerOid);
+    if (funcrettype != LANGUAGE_HANDLEROID)
+    {
         /*
-         * Likewise for the validator, if required; but we don't care about
-         * its return type.
+         * We allow OPAQUE just so we can load old dump files.  When we see a
+         * handler function declared OPAQUE, change it to LANGUAGE_HANDLER.
+         * (This is probably obsolete and removable?)
          */
-        if (pltemplate->tmplvalidator)
+        if (funcrettype == OPAQUEOID)
         {
-            funcname = SystemFuncName(pltemplate->tmplvalidator);
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(valOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplvalidator,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplvalidator,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                valOid = tmpAddr.objectId;
-            }
+            ereport(WARNING,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("changing return type of function %s from %s to %s",
+                            NameListToString(stmt->plhandler),
+                            "opaque", "language_handler")));
+            SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
         }
         else
-            valOid = InvalidOid;
+            ereport(ERROR,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("function %s must return type %s",
+                            NameListToString(stmt->plhandler), "language_handler")));
+    }

-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, pltemplate->tmpltrusted);
+    /* validate the inline function */
+    if (stmt->plinline)
+    {
+        funcargtypes[0] = INTERNALOID;
+        inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
     else
-    {
-        /*
-         * No template, so use the provided information.  If there's no
-         * handler clause, the user is trying to rely on a template that we
-         * don't have, so complain accordingly.
-         */
-        if (!stmt->plhandler)
-            ereport(ERROR,
-                    (errcode(ERRCODE_UNDEFINED_OBJECT),
-                     errmsg("unsupported language \"%s\"",
-                            stmt->plname),
-                     errhint("The supported languages are listed in the pg_pltemplate system catalog.")));
+        inlineOid = InvalidOid;

-        /*
-         * Check permission
-         */
-        if (!superuser())
-            ereport(ERROR,
-                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                     errmsg("must be superuser to create custom procedural language")));
-
-        /*
-         * Lookup the PL handler function and check that it is of the expected
-         * return type
-         */
-        handlerOid = LookupFuncName(stmt->plhandler, 0, funcargtypes, false);
-        funcrettype = get_func_rettype(handlerOid);
-        if (funcrettype != LANGUAGE_HANDLEROID)
-        {
-            /*
-             * We allow OPAQUE just so we can load old dump files.  When we
-             * see a handler function declared OPAQUE, change it to
-             * LANGUAGE_HANDLER.  (This is probably obsolete and removable?)
-             */
-            if (funcrettype == OPAQUEOID)
-            {
-                ereport(WARNING,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("changing return type of function %s from %s to %s",
-                                NameListToString(stmt->plhandler),
-                                "opaque", "language_handler")));
-                SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
-            }
-            else
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(stmt->plhandler), "language_handler")));
-        }
-
-        /* validate the inline function */
-        if (stmt->plinline)
-        {
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            inlineOid = InvalidOid;
-
-        /* validate the validator function */
-        if (stmt->plvalidator)
-        {
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            valOid = InvalidOid;
-
-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, stmt->pltrusted);
+    /* validate the validator function */
+    if (stmt->plvalidator)
+    {
+        funcargtypes[0] = OIDOID;
+        valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
-}
-
-/*
- * Guts of language creation.
- */
-static ObjectAddress
-create_proc_lang(const char *languageName, bool replace,
-                 Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                 Oid valOid, bool trusted)
-{
-    Relation    rel;
-    TupleDesc    tupDesc;
-    Datum        values[Natts_pg_language];
-    bool        nulls[Natts_pg_language];
-    bool        replaces[Natts_pg_language];
-    NameData    langname;
-    HeapTuple    oldtup;
-    HeapTuple    tup;
-    Oid            langoid;
-    bool        is_update;
-    ObjectAddress myself,
-                referenced;
+    else
+        valOid = InvalidOid;

+    /* ok to create it */
     rel = table_open(LanguageRelationId, RowExclusiveLock);
     tupDesc = RelationGetDescr(rel);

@@ -348,7 +129,7 @@ create_proc_lang(const char *languageName, bool replace,
     values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname);
     values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner);
     values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true);
-    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(trusted);
+    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(stmt->pltrusted);
     values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
     values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
     values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
@@ -362,13 +143,17 @@ create_proc_lang(const char *languageName, bool replace,
         Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup);

         /* There is one; okay to replace it? */
-        if (!replace)
+        if (!stmt->replace)
             ereport(ERROR,
                     (errcode(ERRCODE_DUPLICATE_OBJECT),
                      errmsg("language \"%s\" already exists", languageName)));
+
+        /* This is currently pointless, since we already checked superuser */
+#ifdef NOT_USED
         if (!pg_language_ownercheck(oldform->oid, languageOwner))
             aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE,
                            languageName);
+#endif

         /*
          * Do not change existing oid, ownership or permissions.  Note
@@ -451,83 +236,6 @@ create_proc_lang(const char *languageName, bool replace,
 }

 /*
- * Look to see if we have template information for the given language name.
- */
-static PLTemplate *
-find_language_template(const char *languageName)
-{
-    PLTemplate *result;
-    Relation    rel;
-    SysScanDesc scan;
-    ScanKeyData key;
-    HeapTuple    tup;
-
-    rel = table_open(PLTemplateRelationId, AccessShareLock);
-
-    ScanKeyInit(&key,
-                Anum_pg_pltemplate_tmplname,
-                BTEqualStrategyNumber, F_NAMEEQ,
-                CStringGetDatum(languageName));
-    scan = systable_beginscan(rel, PLTemplateNameIndexId, true,
-                              NULL, 1, &key);
-
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_pltemplate tmpl = (Form_pg_pltemplate) GETSTRUCT(tup);
-        Datum        datum;
-        bool        isnull;
-
-        result = (PLTemplate *) palloc0(sizeof(PLTemplate));
-        result->tmpltrusted = tmpl->tmpltrusted;
-        result->tmpldbacreate = tmpl->tmpldbacreate;
-
-        /* Remaining fields are variable-width so we need heap_getattr */
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplhandler,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplhandler = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplinline,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplinline = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplvalidator,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplvalidator = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmpllibrary = TextDatumGetCString(datum);
-
-        /* Ignore template if handler or library info is missing */
-        if (!result->tmplhandler || !result->tmpllibrary)
-            result = NULL;
-    }
-    else
-        result = NULL;
-
-    systable_endscan(scan);
-
-    table_close(rel, AccessShareLock);
-
-    return result;
-}
-
-
-/*
- * This just returns true if we have a valid template for a given language
- */
-bool
-PLTemplateExists(const char *languageName)
-{
-    return (find_language_template(languageName) != NULL);
-}
-
-/*
  * Guts of language dropping.
  */
 void
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf..9bbc8a6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4302,14 +4302,17 @@ NumericOnly_list:    NumericOnly                        { $$ = list_make1($1); }
 CreatePLangStmt:
             CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
             {
-                CreatePLangStmt *n = makeNode(CreatePLangStmt);
-                n->replace = $2;
-                n->plname = $6;
-                /* parameters are all to be supplied by system */
-                n->plhandler = NIL;
-                n->plinline = NIL;
-                n->plvalidator = NIL;
-                n->pltrusted = false;
+                /*
+                 * We now interpret parameterless CREATE LANGUAGE as
+                 * CREATE EXTENSION.  "OR REPLACE" is silently translated
+                 * to "IF NOT EXISTS", which isn't quite the same, but
+                 * seems more useful than throwing an error.  We just
+                 * ignore TRUSTED, as the previous code would have too.
+                 */
+                CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
+                n->if_not_exists = $2;
+                n->extname = $6;
+                n->options = NIL;
                 $$ = (Node *)n;
             }
             | CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 02fc17d..a6a27a6 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -47,6 +47,7 @@ extern ObjectAddress ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *

 extern Oid    get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
+extern bool extension_file_exists(const char *extensionName);

 extern ObjectAddress AlterExtensionNamespace(const char *extensionName, const char *newschema,
                                              Oid *oldschema);
diff --git a/src/include/commands/proclang.h b/src/include/commands/proclang.h
index 9a4bc75..d7b0ab5 100644
--- a/src/include/commands/proclang.h
+++ b/src/include/commands/proclang.h
@@ -1,11 +1,12 @@
-/*
- * src/include/commands/proclang.h
- *
- *-------------------------------------------------------------------------
+/*-------------------------------------------------------------------------
  *
  * proclang.h
  *      prototypes for proclang.c.
  *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/proclang.h
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +18,7 @@

 extern ObjectAddress CreateProceduralLanguage(CreatePLangStmt *stmt);
 extern void DropProceduralLanguageById(Oid langOid);
-extern bool PLTemplateExists(const char *languageName);
+
 extern Oid    get_language_oid(const char *langname, bool missing_ok);

 #endif                            /* PROCLANG_H */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index a573dfb..03ad677 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,11 +226,6 @@
      </row>

      <row>
-      <entry><link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link></entry>
-      <entry>template data for procedural languages</entry>
-     </row>
-
-     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -4911,113 +4906,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
  </sect1>


- <sect1 id="catalog-pg-pltemplate">
-  <title><structname>pg_pltemplate</structname></title>
-
-  <indexterm zone="catalog-pg-pltemplate">
-   <primary>pg_pltemplate</primary>
-  </indexterm>
-
-  <para>
-   The catalog <structname>pg_pltemplate</structname> stores
-   <quote>template</quote> information for procedural languages.
-   A template for a language allows the language to be created in a
-   particular database by a simple <command>CREATE LANGUAGE</command> command,
-   with no need to specify implementation details.
-  </para>
-
-  <para>
-   Unlike most system catalogs, <structname>pg_pltemplate</structname>
-   is shared across all databases of a cluster: there is only one
-   copy of <structname>pg_pltemplate</structname> per cluster, not
-   one per database.  This allows the information to be accessible in
-   each database as it is needed.
-  </para>
-
-  <table>
-   <title><structname>pg_pltemplate</structname> Columns</title>
-
-   <tgroup cols="3">
-    <thead>
-     <row>
-      <entry>Name</entry>
-      <entry>Type</entry>
-      <entry>Description</entry>
-     </row>
-    </thead>
-
-    <tbody>
-     <row>
-      <entry><structfield>tmplname</structfield></entry>
-      <entry><type>name</type></entry>
-      <entry>Name of the language this template is for</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpltrusted</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language is considered trusted</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpldbacreate</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language may be created by a database owner</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplhandler</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of call handler function</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplinline</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of anonymous-block handler function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplvalidator</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of validator function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpllibrary</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Path of shared library that implements language</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplacl</structfield></entry>
-      <entry><type>aclitem[]</type></entry>
-      <entry>Access privileges for template (not actually used)</entry>
-     </row>
-
-    </tbody>
-   </tgroup>
-  </table>
-
-  <para>
-   There are not currently any commands that manipulate procedural language
-   templates; to change the built-in information, a superuser must modify
-   the table using ordinary <command>INSERT</command>, <command>DELETE</command>,
-   or <command>UPDATE</command> commands.
-  </para>
-
-  <note>
-   <para>
-    It is likely that <structname>pg_pltemplate</structname> will be removed in some
-    future release of <productname>PostgreSQL</productname>, in favor of
-    keeping this knowledge about procedural languages in their respective
-    extension installation scripts.
-   </para>
-  </note>
-
- </sect1>
-
-
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>

diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 4c2f7e8..388a774 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -153,7 +153,7 @@
      <para>
       Daredevils, who want to build a Python-3-only operating system
       environment, can change the contents of
-      <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
+      <literal>plpythonu</literal>'s extension control and script files
       to make <literal>plpythonu</literal> be equivalent
       to <literal>plpython3u</literal>, keeping in mind that this
       would make their installation incompatible with most of the rest
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a511532..1451b78 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -60,7 +60,7 @@ CATALOG_HEADERS := \
     pg_statistic_ext.h pg_statistic_ext_data.h \
     pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
     pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
-    pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
+    pg_database.h pg_db_role_setting.h pg_tablespace.h \
     pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
     pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
     pg_ts_parser.h pg_ts_template.h pg_extension.h \
@@ -86,7 +86,7 @@ POSTGRES_BKI_DATA = $(addprefix $(top_srcdir)/src/include/catalog/,\
     pg_cast.dat pg_class.dat pg_collation.dat pg_conversion.dat \
     pg_database.dat pg_language.dat \
     pg_namespace.dat pg_opclass.dat pg_operator.dat pg_opfamily.dat \
-    pg_pltemplate.dat pg_proc.dat pg_range.dat pg_tablespace.dat \
+    pg_proc.dat pg_range.dat pg_tablespace.dat \
     pg_ts_config.dat pg_ts_config_map.dat pg_ts_dict.dat pg_ts_parser.dat \
     pg_ts_template.dat pg_type.dat \
     )
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 1af31c2..8d424de 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -32,7 +32,6 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
@@ -243,7 +242,6 @@ IsSharedRelation(Oid relationId)
     if (relationId == AuthIdRelationId ||
         relationId == AuthMemRelationId ||
         relationId == DatabaseRelationId ||
-        relationId == PLTemplateRelationId ||
         relationId == SharedDescriptionRelationId ||
         relationId == SharedDependRelationId ||
         relationId == SharedSecLabelRelationId ||
@@ -259,7 +257,6 @@ IsSharedRelation(Oid relationId)
         relationId == AuthMemMemRoleIndexId ||
         relationId == DatabaseNameIndexId ||
         relationId == DatabaseOidIndexId ||
-        relationId == PLTemplateNameIndexId ||
         relationId == SharedDescriptionObjIndexId ||
         relationId == SharedDependDependerIndexId ||
         relationId == SharedDependReferenceIndexId ||
@@ -279,8 +276,6 @@ IsSharedRelation(Oid relationId)
         relationId == PgDatabaseToastIndex ||
         relationId == PgDbRoleSettingToastTable ||
         relationId == PgDbRoleSettingToastIndex ||
-        relationId == PgPlTemplateToastTable ||
-        relationId == PgPlTemplateToastIndex ||
         relationId == PgReplicationOriginToastTable ||
         relationId == PgReplicationOriginToastIndex ||
         relationId == PgShdescriptionToastTable ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bf69adc..6b0fd42 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11396,9 +11396,9 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
     }

     /*
-     * If the functions are dumpable then emit a traditional CREATE LANGUAGE
-     * with parameters.  Otherwise, we'll write a parameterless command, which
-     * will rely on data from pg_pltemplate.
+     * If the functions are dumpable then emit a complete CREATE LANGUAGE with
+     * parameters.  Otherwise, we'll write a parameterless command, which will
+     * be interpreted as CREATE EXTENSION.
      */
     useParams = (funcInfo != NULL &&
                  (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
@@ -11431,11 +11431,11 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
         /*
          * If not dumping parameters, then use CREATE OR REPLACE so that the
          * command will not fail if the language is preinstalled in the target
-         * database.  We restrict the use of REPLACE to this case so as to
-         * eliminate the risk of replacing a language with incompatible
-         * parameter settings: this command will only succeed at all if there
-         * is a pg_pltemplate entry, and if there is one, the existing entry
-         * must match it too.
+         * database.
+         *
+         * Modern servers will interpret this as CREATE EXTENSION IF NOT
+         * EXISTS; perhaps we should emit that instead?  But it might just add
+         * confusion.
          */
         appendPQExpBuffer(defqry, "CREATE OR REPLACE PROCEDURAL LANGUAGE %s",
                           qlanname);
diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c
index ac984db..a239c53 100644
--- a/src/bin/pg_upgrade/function.c
+++ b/src/bin/pg_upgrade/function.c
@@ -214,13 +214,9 @@ check_loadable_libraries(void)
              * plpython2u language was created with library name plpython2.so
              * as a symbolic link to plpython.so.  In Postgres 9.1, only the
              * plpython2.so library was created, and both plpythonu and
-             * plpython2u pointing to it.  For this reason, any reference to
+             * plpython2u point to it.  For this reason, any reference to
              * library name "plpython" in an old PG <= 9.1 cluster must look
              * for "plpython2" in the new cluster.
-             *
-             * For this case, we could check pg_pltemplate, but that only
-             * works for languages, and does not help with function shared
-             * objects, so we just do a general fix.
              */
             if (GET_MAJOR_VERSION(old_cluster.major_version) < 901 &&
                 strcmp(lib, "$libdir/plpython") == 0)
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ef4445b..7294d59 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -206,9 +206,6 @@ DECLARE_UNIQUE_INDEX(pg_opfamily_am_name_nsp_index, 2754, on pg_opfamily using b
 DECLARE_UNIQUE_INDEX(pg_opfamily_oid_index, 2755, on pg_opfamily using btree(oid oid_ops));
 #define OpfamilyOidIndexId    2755

-DECLARE_UNIQUE_INDEX(pg_pltemplate_name_index, 1137, on pg_pltemplate using btree(tmplname name_ops));
-#define PLTemplateNameIndexId  1137
-
 DECLARE_UNIQUE_INDEX(pg_proc_oid_index, 2690, on pg_proc using btree(oid oid_ops));
 #define ProcedureOidIndexId  2690
 DECLARE_UNIQUE_INDEX(pg_proc_proname_args_nsp_index, 2691, on pg_proc using btree(proname name_ops, proargtypes
oidvector_ops,pronamespace oid_ops)); 
diff --git a/src/include/catalog/pg_pltemplate.dat b/src/include/catalog/pg_pltemplate.dat
deleted file mode 100644
index 6f6d167..0000000
--- a/src/include/catalog/pg_pltemplate.dat
+++ /dev/null
@@ -1,51 +0,0 @@
-#----------------------------------------------------------------------
-#
-# pg_pltemplate.dat
-#    Initial contents of the pg_pltemplate system catalog.
-#
-# Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
-# Portions Copyright (c) 1994, Regents of the University of California
-#
-# src/include/catalog/pg_pltemplate.dat
-#
-#----------------------------------------------------------------------
-
-[
-
-{ tmplname => 'plpgsql', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plpgsql_call_handler', tmplinline => 'plpgsql_inline_handler',
-  tmplvalidator => 'plpgsql_validator', tmpllibrary => '$libdir/plpgsql',
-  tmplacl => '_null_' },
-{ tmplname => 'pltcl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'pltcl_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'pltclu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'pltclu_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plperl_call_handler', tmplinline => 'plperl_inline_handler',
-  tmplvalidator => 'plperl_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperlu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plperlu_call_handler', tmplinline => 'plperlu_inline_handler',
-  tmplvalidator => 'plperlu_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plpythonu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython_call_handler',
-  tmplinline => 'plpython_inline_handler',
-  tmplvalidator => 'plpython_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython2u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython2_call_handler',
-  tmplinline => 'plpython2_inline_handler',
-  tmplvalidator => 'plpython2_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython3u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython3_call_handler',
-  tmplinline => 'plpython3_inline_handler',
-  tmplvalidator => 'plpython3_validator', tmpllibrary => '$libdir/plpython3',
-  tmplacl => '_null_' },
-
-]
diff --git a/src/include/catalog/pg_pltemplate.h b/src/include/catalog/pg_pltemplate.h
deleted file mode 100644
index ce89000..0000000
--- a/src/include/catalog/pg_pltemplate.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * pg_pltemplate.h
- *      definition of the "PL template" system catalog (pg_pltemplate)
- *
- *
- * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/catalog/pg_pltemplate.h
- *
- * NOTES
- *      The Catalog.pm module reads this file and derives schema
- *      information.
- *
- *-------------------------------------------------------------------------
- */
-#ifndef PG_PLTEMPLATE_H
-#define PG_PLTEMPLATE_H
-
-#include "catalog/genbki.h"
-#include "catalog/pg_pltemplate_d.h"
-
-/* ----------------
- *        pg_pltemplate definition.  cpp turns this into
- *        typedef struct FormData_pg_pltemplate
- * ----------------
- */
-CATALOG(pg_pltemplate,1136,PLTemplateRelationId) BKI_SHARED_RELATION
-{
-    NameData    tmplname;        /* name of PL */
-    bool        tmpltrusted;    /* PL is trusted? */
-    bool        tmpldbacreate;    /* PL is installable by db owner? */
-
-#ifdef CATALOG_VARLEN            /* variable-length fields start here */
-    text        tmplhandler BKI_FORCE_NOT_NULL; /* name of call handler
-                                                 * function */
-    text        tmplinline;        /* name of anonymous-block handler, or NULL */
-    text        tmplvalidator;    /* name of validator function, or NULL */
-    text        tmpllibrary BKI_FORCE_NOT_NULL; /* path of shared library */
-    aclitem        tmplacl[1];        /* access privileges for template */
-#endif
-} FormData_pg_pltemplate;
-
-/* ----------------
- *        Form_pg_pltemplate corresponds to a pointer to a row with
- *        the format of pg_pltemplate relation.
- * ----------------
- */
-typedef FormData_pg_pltemplate *Form_pg_pltemplate;
-
-#endif                            /* PG_PLTEMPLATE_H */
diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h
index cc5dfed..5a047fc 100644
--- a/src/include/catalog/toasting.h
+++ b/src/include/catalog/toasting.h
@@ -86,9 +86,6 @@ DECLARE_TOAST(pg_database, 4177, 4178);
 DECLARE_TOAST(pg_db_role_setting, 2966, 2967);
 #define PgDbRoleSettingToastTable 2966
 #define PgDbRoleSettingToastIndex 2967
-DECLARE_TOAST(pg_pltemplate, 4179, 4180);
-#define PgPlTemplateToastTable 4179
-#define PgPlTemplateToastIndex 4180
 DECLARE_TOAST(pg_replication_origin, 4181, 4182);
 #define PgReplicationOriginToastTable 4181
 #define PgReplicationOriginToastIndex 4182
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 6edfcf2..0d9564e 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -33,7 +33,7 @@
  */

 #if PY_MAJOR_VERSION >= 3
-/* Use separate names to avoid clash in pg_pltemplate */
+/* Use separate names to reduce confusion */
 #define plpython_validator plpython3_validator
 #define plpython_call_handler plpython3_call_handler
 #define plpython_inline_handler plpython3_inline_handler
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 070de78..f0cd40b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -134,7 +134,6 @@ pg_opclass|t
 pg_operator|t
 pg_opfamily|t
 pg_partitioned_table|t
-pg_pltemplate|t
 pg_policy|t
 pg_proc|t
 pg_publication|t

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
I wrote:
> Accordingly, here's a patchset that does it like that.

The cfbot noticed that a couple of patches committed this week
created (trivial) conflicts with this patchset.  Here's a v3
rebased up to HEAD; no interesting changes.

            regards, tom lane

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 55694c4..a573dfb 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8514,7 +8514,15 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
      <row>
       <entry><structfield>superuser</structfield></entry>
       <entry><type>bool</type></entry>
-      <entry>True if only superusers are allowed to install this extension</entry>
+      <entry>True if only superusers are allowed to install this extension
+       (but see <structfield>trusted</structfield>)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>trusted</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry>True if the extension can be installed by non-superusers
+       with appropriate privileges</entry>
      </row>

      <row>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f2..e2807d0 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -576,6 +576,32 @@
         version.  If it is set to <literal>false</literal>, just the privileges
         required to execute the commands in the installation or update script
         are required.
+        This should normally be set to <literal>true</literal> if any of the
+        script commands require superuser privileges.  (Such commands would
+        fail anyway, but it's more user-friendly to give the error up front.)
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>trusted</varname> (<type>boolean</type>)</term>
+      <listitem>
+       <para>
+        This parameter, if set to <literal>true</literal> (which is not the
+        default), allows some non-superusers to install an extension that
+        has <varname>superuser</varname> set to <literal>true</literal>.
+        Specifically, installation will be permitted for the owner of the
+        current database, and for anyone who has been granted
+        the <literal>pg_install_trusted_extension</literal> role.
+        When the user executing <command>CREATE EXTENSION</command> is not
+        a superuser but is allowed to install by virtue of this parameter,
+        then the installation or update script is run as the bootstrap
+        superuser, not as the calling user.
+        This parameter is irrelevant if <varname>superuser</varname> is
+        <literal>false</literal>.
+        Generally, this should not be set true for extensions that could
+        allow access to otherwise-superuser-only abilities, such as
+        filesystem access.
        </para>
       </listitem>
      </varlistentry>
@@ -642,6 +668,18 @@
     </para>

     <para>
+     If the extension script contains the
+     string <literal>@extowner@</literal>, that string is replaced with the
+     (suitably quoted) name of the user calling <command>CREATE
+     EXTENSION</command> or <command>ALTER EXTENSION</command>.  Typically
+     this feature is used by extensions that are marked trusted to assign
+     ownership of selected objects to the calling user rather than the
+     bootstrap superuser.  (One should be careful about doing so, however.
+     For example, assigning ownership of a C-language function to a
+     non-superuser would create a privilege escalation path for that user.)
+    </para>
+
+    <para>
      While the script files can contain any characters allowed by the specified
      encoding, control files should contain only plain ASCII, because there
      is no way for <productname>PostgreSQL</productname> to know what encoding a
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index 36837f9..7cd4346 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -47,14 +47,26 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
   </para>

   <para>
-   Loading an extension requires the same privileges that would be
-   required to create its component objects.  For most extensions this
-   means superuser or database owner privileges are needed.
    The user who runs <command>CREATE EXTENSION</command> becomes the
    owner of the extension for purposes of later privilege checks, as well
    as the owner of any objects created by the extension's script.
   </para>

+  <para>
+   Loading an extension ordinarily requires the same privileges that would
+   be required to create its component objects.  For many extensions this
+   means superuser privileges are needed.
+   However, if the extension is marked <firstterm>trusted</firstterm> in
+   its control file, then it can be installed by a non-superuser who has
+   suitable privileges (that is, owns the current database or has been
+   granted the <literal>pg_install_trusted_extension</literal> role).  In
+   this case the extension object itself will be owned by the calling user,
+   but the contained objects will be owned by the bootstrap superuser
+   (unless the extension's script explicitly assigns them to the calling
+   user).  This configuration gives the calling user the right to drop the
+   extension, but not to modify individual objects within it.
+  </para>
+
  </refsect1>

  <refsect1>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 66f1627..90f637f 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -556,6 +556,10 @@ DROP ROLE doomed_role;
        <entry>Allow executing programs on the database server as the user the database runs as with
        COPY and other functions which allow executing a server-side program.</entry>
       </row>
+      <row>
+       <entry>pg_install_trusted_extension</entry>
+       <entry>Allow installation of <quote>trusted</quote> extensions.</entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
@@ -589,6 +593,17 @@ DROP ROLE doomed_role;
   </para>

   <para>
+  The <literal>pg_install_trusted_extension</literal> role allows grantees
+  to install <link linkend="extend-extensions">extensions</link> that are
+  marked <quote>trusted</quote> in their control files.  This is a
+  privilege that is available automatically to database owners, but
+  granting this role allows administrators to let other non-superuser roles
+  do it too.  Generally, unless the <quote>trusted</quote> marking has been
+  applied to extensions incautiously, granting this role carries no large
+  security risk.
+  </para>
+
+  <para>
   Care should be taken when granting these roles to ensure they are only used where
   needed and with the understanding that these roles grant access to privileged
   information.
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 4456fef..fb046a2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -317,7 +317,8 @@ CREATE VIEW pg_available_extensions AS

 CREATE VIEW pg_available_extension_versions AS
     SELECT E.name, E.version, (X.extname IS NOT NULL) AS installed,
-           E.superuser, E.relocatable, E.schema, E.requires, E.comment
+           E.superuser, E.trusted, E.relocatable,
+           E.schema, E.requires, E.comment
       FROM pg_available_extension_versions() AS E
            LEFT JOIN pg_extension AS X
              ON E.name = X.extname AND E.version = X.extversion;
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index a04b0c9..bf66a1c 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -40,6 +40,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
@@ -84,6 +85,7 @@ typedef struct ExtensionControlFile
     char       *schema;            /* target schema (allowed if !relocatable) */
     bool        relocatable;    /* is ALTER EXTENSION SET SCHEMA supported? */
     bool        superuser;        /* must be superuser to install? */
+    bool        trusted;        /* allow becoming superuser on the fly? */
     int            encoding;        /* encoding of the script file, or -1 */
     List       *requires;        /* names of prerequisite extensions */
 } ExtensionControlFile;
@@ -558,6 +560,14 @@ parse_extension_control_file(ExtensionControlFile *control,
                          errmsg("parameter \"%s\" requires a Boolean value",
                                 item->name)));
         }
+        else if (strcmp(item->name, "trusted") == 0)
+        {
+            if (!parse_bool(item->value, &control->trusted))
+                ereport(ERROR,
+                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                         errmsg("parameter \"%s\" requires a Boolean value",
+                                item->name)));
+        }
         else if (strcmp(item->name, "encoding") == 0)
         {
             control->encoding = pg_valid_server_encoding(item->value);
@@ -614,6 +624,7 @@ read_extension_control_file(const char *extname)
     control->name = pstrdup(extname);
     control->relocatable = false;
     control->superuser = true;
+    control->trusted = false;
     control->encoding = -1;

     /*
@@ -795,6 +806,27 @@ execute_sql_string(const char *sql)
 }

 /*
+ * Policy function: is the given extension trusted for installation by a
+ * non-superuser?
+ *
+ * (Update the errhint logic below if you change this.)
+ */
+static bool
+extension_is_trusted(ExtensionControlFile *control)
+{
+    /* Never trust unless extension's control file says it's okay */
+    if (!control->trusted)
+        return false;
+    /* Database owner can install */
+    if (pg_database_ownercheck(MyDatabaseId, GetUserId()))
+        return true;
+    /* Members of pg_install_trusted_extension can install */
+    if (has_privs_of_role(GetUserId(), DEFAULT_ROLE_INSTALL_TRUSTED_EXTENSION))
+        return true;
+    return false;
+}
+
+/*
  * Execute the appropriate script file for installing or updating the extension
  *
  * If from_version isn't NULL, it's an update
@@ -806,35 +838,56 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                          List *requiredSchemas,
                          const char *schemaName, Oid schemaOid)
 {
+    bool        switch_to_superuser = false;
     char       *filename;
+    Oid            save_userid = 0;
+    int            save_sec_context = 0;
     int            save_nestlevel;
     StringInfoData pathbuf;
     ListCell   *lc;

     /*
-     * Enforce superuser-ness if appropriate.  We postpone this check until
-     * here so that the flag is correctly associated with the right script(s)
-     * if it's set in secondary control files.
+     * Enforce superuser-ness if appropriate.  We postpone these checks until
+     * here so that the control flags are correctly associated with the right
+     * script(s) if they happen to be set in secondary control files.
      */
     if (control->superuser && !superuser())
     {
-        if (from_version == NULL)
+        if (extension_is_trusted(control))
+            switch_to_superuser = true;
+        else if (from_version == NULL)
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("permission denied to create extension \"%s\"",
                             control->name),
-                     errhint("Must be superuser to create this extension.")));
+                     control->trusted
+                     ? errhint("Must be database owner or member of pg_install_trusted_extension to create this
extension.")
+                     : errhint("Must be superuser to create this extension.")));
         else
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("permission denied to update extension \"%s\"",
                             control->name),
-                     errhint("Must be superuser to update this extension.")));
+                     control->trusted
+                     ? errhint("Must be database owner or member of pg_install_trusted_extension to update this
extension.")
+                     : errhint("Must be superuser to update this extension.")));
     }

     filename = get_extension_script_filename(control, from_version, version);

     /*
+     * If installing a trusted extension on behalf of a non-superuser, become
+     * the bootstrap superuser.  (This switch will be cleaned up automatically
+     * if the transaction aborts, as will the GUC changes below.)
+     */
+    if (switch_to_superuser)
+    {
+        GetUserIdAndSecContext(&save_userid, &save_sec_context);
+        SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
+                               save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+    }
+
+    /*
      * 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.
@@ -907,6 +960,22 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                                         CStringGetTextDatum("ng"));

         /*
+         * If the script uses @extowner@, substitute the calling username.
+         */
+        if (strstr(c_sql, "@extowner@"))
+        {
+            Oid            uid = switch_to_superuser ? save_userid : GetUserId();
+            const char *userName = GetUserNameFromId(uid, false);
+            const char *qUserName = quote_identifier(userName);
+
+            t_sql = DirectFunctionCall3Coll(replace_text,
+                                            C_COLLATION_OID,
+                                            t_sql,
+                                            CStringGetTextDatum("@extowner@"),
+                                            CStringGetTextDatum(qUserName));
+        }
+
+        /*
          * If it's not relocatable, substitute the target schema name for
          * occurrences of @extschema@.
          *
@@ -953,6 +1022,12 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
      * Restore the GUC variables we set above.
      */
     AtEOXact_GUC(true, save_nestlevel);
+
+    /*
+     * Restore authentication state if needed.
+     */
+    if (switch_to_superuser)
+        SetUserIdAndSecContext(save_userid, save_sec_context);
 }

 /*
@@ -2113,8 +2188,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
     {
         ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
         ExtensionControlFile *control;
-        Datum        values[7];
-        bool        nulls[7];
+        Datum        values[8];
+        bool        nulls[8];
         ListCell   *lc2;

         if (!evi->installable)
@@ -2135,24 +2210,26 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
         values[1] = CStringGetTextDatum(evi->name);
         /* superuser */
         values[2] = BoolGetDatum(control->superuser);
+        /* trusted */
+        values[3] = BoolGetDatum(control->trusted);
         /* relocatable */
-        values[3] = BoolGetDatum(control->relocatable);
+        values[4] = BoolGetDatum(control->relocatable);
         /* schema */
         if (control->schema == NULL)
-            nulls[4] = true;
+            nulls[5] = true;
         else
-            values[4] = DirectFunctionCall1(namein,
+            values[5] = DirectFunctionCall1(namein,
                                             CStringGetDatum(control->schema));
         /* requires */
         if (control->requires == NIL)
-            nulls[5] = true;
+            nulls[6] = true;
         else
-            values[5] = convert_requires_to_datum(control->requires);
+            values[6] = convert_requires_to_datum(control->requires);
         /* comment */
         if (control->comment == NULL)
-            nulls[6] = true;
+            nulls[7] = true;
         else
-            values[6] = CStringGetTextDatum(control->comment);
+            values[7] = CStringGetTextDatum(control->comment);

         tuplestore_putvalues(tupstore, tupdesc, values, nulls);

@@ -2180,16 +2257,18 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
                 values[1] = CStringGetTextDatum(evi2->name);
                 /* superuser */
                 values[2] = BoolGetDatum(control->superuser);
+                /* trusted */
+                values[3] = BoolGetDatum(control->trusted);
                 /* relocatable */
-                values[3] = BoolGetDatum(control->relocatable);
+                values[4] = BoolGetDatum(control->relocatable);
                 /* schema stays the same */
                 /* requires */
                 if (control->requires == NIL)
-                    nulls[5] = true;
+                    nulls[6] = true;
                 else
                 {
-                    values[5] = convert_requires_to_datum(control->requires);
-                    nulls[5] = false;
+                    values[6] = convert_requires_to_datum(control->requires);
+                    nulls[6] = false;
                 }
                 /* comment stays the same */

diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index c21f97a..3b04259 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -60,5 +60,10 @@
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '9495', oid_symbol => 'DEFAULT_ROLE_INSTALL_TRUSTED_EXTENSION',
+  rolname => 'pg_install_trusted_extension', rolsuper => 'f', rolinherit => 't',
+  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+  rolpassword => '_null_', rolvaliduntil => '_null_' },

 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 58ea5b9..b50eefb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9459,9 +9459,9 @@
   proname => 'pg_available_extension_versions', procost => '10',
   prorows => '100', proretset => 't', provolatile => 's',
   prorettype => 'record', proargtypes => '',
-  proallargtypes => '{name,text,bool,bool,name,_name,text}',
-  proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,version,superuser,relocatable,schema,requires,comment}',
+  proallargtypes => '{name,text,bool,bool,bool,name,_name,text}',
+  proargmodes => '{o,o,o,o,o,o,o,o}',
+  proargnames => '{name,version,superuser,trusted,relocatable,schema,requires,comment}',
   prosrc => 'pg_available_extension_versions' },
 { oid => '3084', descr => 'list an extension\'s version update paths',
   proname => 'pg_extension_update_paths', procost => '10', prorows => '100',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 14e7214..4b7855b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1311,11 +1311,12 @@ pg_available_extension_versions| SELECT e.name,
     e.version,
     (x.extname IS NOT NULL) AS installed,
     e.superuser,
+    e.trusted,
     e.relocatable,
     e.schema,
     e.requires,
     e.comment
-   FROM (pg_available_extension_versions() e(name, version, superuser, relocatable, schema, requires, comment)
+   FROM (pg_available_extension_versions() e(name, version, superuser, trusted, relocatable, schema, requires,
comment)
      LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion))));
 pg_available_extensions| SELECT e.name,
     e.default_version,
diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile
index 9b1c514..e4d0a0b 100644
--- a/src/pl/plperl/GNUmakefile
+++ b/src/pl/plperl/GNUmakefile
@@ -55,8 +55,10 @@ endif # win32

 SHLIB_LINK = $(perl_embed_ldflags)

-REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-extension=plperl  --load-extension=plperlu
-REGRESS = plperl plperl_lc plperl_trigger plperl_shared plperl_elog plperl_util plperl_init plperlu plperl_array
plperl_callplperl_transaction 
+REGRESS_OPTS = --dbname=$(PL_TESTDB)
+REGRESS = plperl_setup plperl plperl_lc plperl_trigger plperl_shared \
+    plperl_elog plperl_util plperl_init plperlu plperl_array \
+    plperl_call plperl_transaction
 # if Perl can support two interpreters in one backend,
 # test plperl-and-plperlu cases
 ifneq ($(PERL),)
diff --git a/src/pl/plperl/expected/plperl_setup.out b/src/pl/plperl/expected/plperl_setup.out
new file mode 100644
index 0000000..7cb1f25
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_setup.out
@@ -0,0 +1,60 @@
+--
+-- Install the plperl and plperlu extensions
+--
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- suitable permissions have been granted.
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;  -- fail
+ERROR:  permission denied to create extension "plperl"
+HINT:  Must be database owner or member of pg_install_trusted_extension to create this extension.
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+RESET ROLE;
+GRANT pg_install_trusted_extension TO regress_user1;
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+ foo1
+------
+    1
+(1 row)
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+ERROR:  permission denied for language plperl
+SET ROLE regress_user1;
+grant usage on language plperl to regress_user2;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+ foo2
+------
+    2
+(1 row)
+
+SET ROLE regress_user1;
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+ERROR:  cannot drop language plperl because extension plperl requires it
+HINT:  You can drop extension plperl instead.
+DROP EXTENSION plperl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to function foo1()
+drop cascades to function foo2()
+-- Clean up
+RESET ROLE;
+DROP USER regress_user1;
+DROP USER regress_user2;
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plperl/plperl--1.0.sql b/src/pl/plperl/plperl--1.0.sql
index f716ba1..5ff31e7 100644
--- a/src/pl/plperl/plperl--1.0.sql
+++ b/src/pl/plperl/plperl--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plperl/plperl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperl;
+CREATE FUNCTION plperl_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperl_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plperl
+  HANDLER plperl_call_handler
+  INLINE plperl_inline_handler
+  VALIDATOR plperl_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plperl OWNER TO @extowner@;

 COMMENT ON LANGUAGE plperl IS 'PL/Perl procedural language';
diff --git a/src/pl/plperl/plperl.control b/src/pl/plperl/plperl.control
index 6faace1..3a2230a 100644
--- a/src/pl/plperl/plperl.control
+++ b/src/pl/plperl/plperl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plperl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/plperl/plperlu--1.0.sql b/src/pl/plperl/plperlu--1.0.sql
index 7efb4fb..10d7594 100644
--- a/src/pl/plperl/plperlu--1.0.sql
+++ b/src/pl/plperl/plperlu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plperl/plperlu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperlu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperlu;
+CREATE FUNCTION plperlu_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperlu_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plperlu
+  HANDLER plperlu_call_handler
+  INLINE plperlu_inline_handler
+  VALIDATOR plperlu_validator;

 COMMENT ON LANGUAGE plperlu IS 'PL/PerlU untrusted procedural language';
diff --git a/src/pl/plperl/sql/plperl_setup.sql b/src/pl/plperl/sql/plperl_setup.sql
new file mode 100644
index 0000000..9c7b52c
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_setup.sql
@@ -0,0 +1,58 @@
+--
+-- Install the plperl and plperlu extensions
+--
+
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- suitable permissions have been granted.
+
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;  -- fail
+CREATE EXTENSION plperlu;  -- fail
+
+RESET ROLE;
+
+GRANT pg_install_trusted_extension TO regress_user1;
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+
+SET ROLE regress_user1;
+
+grant usage on language plperl to regress_user2;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+
+SET ROLE regress_user1;
+
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+DROP EXTENSION plperl CASCADE;
+
+-- Clean up
+RESET ROLE;
+DROP USER regress_user1;
+DROP USER regress_user2;
+
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plpgsql/src/plpgsql--1.0.sql b/src/pl/plpgsql/src/plpgsql--1.0.sql
index ab6fa84..6e5b990 100644
--- a/src/pl/plpgsql/src/plpgsql--1.0.sql
+++ b/src/pl/plpgsql/src/plpgsql--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plpgsql/src/plpgsql--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpgsql_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpgsql;
+CREATE FUNCTION plpgsql_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpgsql_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plpgsql
+  HANDLER plpgsql_call_handler
+  INLINE plpgsql_inline_handler
+  VALIDATOR plpgsql_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plpgsql OWNER TO @extowner@;

 COMMENT ON LANGUAGE plpgsql IS 'PL/pgSQL procedural language';
diff --git a/src/pl/plpgsql/src/plpgsql.control b/src/pl/plpgsql/src/plpgsql.control
index b320227..42e764b 100644
--- a/src/pl/plpgsql/src/plpgsql.control
+++ b/src/pl/plpgsql/src/plpgsql.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plpgsql'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/plpython/plpython2u--1.0.sql b/src/pl/plpython/plpython2u--1.0.sql
index 661cc66..69f7477 100644
--- a/src/pl/plpython/plpython2u--1.0.sql
+++ b/src/pl/plpython/plpython2u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython2u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython2_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython2u;
+CREATE FUNCTION plpython2_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython2_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython2u
+  HANDLER plpython2_call_handler
+  INLINE plpython2_inline_handler
+  VALIDATOR plpython2_validator;

 COMMENT ON LANGUAGE plpython2u IS 'PL/Python2U untrusted procedural language';
diff --git a/src/pl/plpython/plpython3u--1.0.sql b/src/pl/plpython/plpython3u--1.0.sql
index c0d6ea8..ba2e6ac 100644
--- a/src/pl/plpython/plpython3u--1.0.sql
+++ b/src/pl/plpython/plpython3u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython3u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython3_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython3u;
+CREATE FUNCTION plpython3_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython3_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython3u
+  HANDLER plpython3_call_handler
+  INLINE plpython3_inline_handler
+  VALIDATOR plpython3_validator;

 COMMENT ON LANGUAGE plpython3u IS 'PL/Python3U untrusted procedural language';
diff --git a/src/pl/plpython/plpythonu--1.0.sql b/src/pl/plpython/plpythonu--1.0.sql
index 4a3e64a..4c6f7c3 100644
--- a/src/pl/plpython/plpythonu--1.0.sql
+++ b/src/pl/plpython/plpythonu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpythonu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpythonu;
+CREATE FUNCTION plpython_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpythonu
+  HANDLER plpython_call_handler
+  INLINE plpython_inline_handler
+  VALIDATOR plpython_validator;

 COMMENT ON LANGUAGE plpythonu IS 'PL/PythonU untrusted procedural language';
diff --git a/src/pl/tcl/pltcl--1.0.sql b/src/pl/tcl/pltcl--1.0.sql
index 34a68c8..2ed2b92 100644
--- a/src/pl/tcl/pltcl--1.0.sql
+++ b/src/pl/tcl/pltcl--1.0.sql
@@ -1,11 +1,12 @@
 /* src/pl/tcl/pltcl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltcl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltcl;
+CREATE TRUSTED LANGUAGE pltcl
+  HANDLER pltcl_call_handler;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE pltcl OWNER TO @extowner@;

 COMMENT ON LANGUAGE pltcl IS 'PL/Tcl procedural language';
diff --git a/src/pl/tcl/pltcl.control b/src/pl/tcl/pltcl.control
index b9dc1b8..1568c17 100644
--- a/src/pl/tcl/pltcl.control
+++ b/src/pl/tcl/pltcl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/pltcl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/tcl/pltclu--1.0.sql b/src/pl/tcl/pltclu--1.0.sql
index e05b470..fca869f 100644
--- a/src/pl/tcl/pltclu--1.0.sql
+++ b/src/pl/tcl/pltclu--1.0.sql
@@ -1,11 +1,9 @@
 /* src/pl/tcl/pltclu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltclu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltclu;
+CREATE LANGUAGE pltclu
+  HANDLER pltclu_call_handler;

 COMMENT ON LANGUAGE pltclu IS 'PL/TclU untrusted procedural language';
diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
index 13b28b1..2a9c7e8 100644
--- a/doc/src/sgml/ref/create_language.sgml
+++ b/doc/src/sgml/ref/create_language.sgml
@@ -21,9 +21,9 @@ PostgreSQL documentation

  <refsynopsisdiv>
 <synopsis>
-CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable
class="parameter">inline_handler</replaceable>] [ VALIDATOR <replaceable>valfunction</replaceable> ] 
+CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 </synopsis>
  </refsynopsisdiv>

@@ -37,21 +37,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
    defined in this new language.
   </para>

-  <note>
-   <para>
-    As of <productname>PostgreSQL</productname> 9.1, most procedural
-    languages have been made into <quote>extensions</quote>, and should
-    therefore be installed with <xref linkend="sql-createextension"/>
-    not <command>CREATE LANGUAGE</command>.  Direct use of
-    <command>CREATE LANGUAGE</command> should now be confined to
-    extension installation scripts.  If you have a <quote>bare</quote>
-    language in your database, perhaps as a result of an upgrade,
-    you can convert it to an extension using
-    <literal>CREATE EXTENSION <replaceable>langname</replaceable> FROM
-    unpackaged</literal>.
-   </para>
-  </note>
-
   <para>
    <command>CREATE LANGUAGE</command> effectively associates the
    language name with handler function(s) that are responsible for executing
@@ -60,53 +45,32 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   There are two forms of the <command>CREATE LANGUAGE</command> command.
-   In the first form, the user supplies just the name of the desired
-   language, and the <productname>PostgreSQL</productname> server consults
-   the <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
-   system catalog to determine the correct parameters.  In the second form,
-   the user supplies the language parameters along with the language name.
-   The second form can be used to create a language that is not defined in
-   <structname>pg_pltemplate</structname>, but this approach is considered obsolescent.
-  </para>
-
-  <para>
-   When the server finds an entry in the <structname>pg_pltemplate</structname> catalog
-   for the given language name, it will use the catalog data even if the
-   command includes language parameters.  This behavior simplifies loading of
-   old dump files, which are likely to contain out-of-date information
-   about language support functions.
-  </para>
-
-  <para>
-   Ordinarily, the user must have the
-   <productname>PostgreSQL</productname> superuser privilege to
-   register a new language.  However, the owner of a database can register
-   a new language within that database if the language is listed in
-   the <structname>pg_pltemplate</structname> catalog and is marked
-   as allowed to be created by database owners (<structfield>tmpldbacreate</structfield>
-   is true).  The default is that trusted languages can be created
-   by database owners, but this can be adjusted by superusers by modifying
-   the contents of <structname>pg_pltemplate</structname>.
-   The creator of a language becomes its owner and can later
-   drop it, rename it, or assign it to a new owner.
+   The form of <command>CREATE LANGUAGE</command> that does not supply
+   any handler function is obsolete.  For backwards compatibility with
+   old dump files, it is interpreted as <command>CREATE EXTENSION</command>.
+   That will work if the language has been packaged into an extension of
+   the same name, which is the conventional way to set up procedural
+   languages.
   </para>

   <para>
    <command>CREATE OR REPLACE LANGUAGE</command> will either create a
    new language, or replace an existing definition.  If the language
-   already exists, its parameters are updated according to the values
-   specified or taken from <structname>pg_pltemplate</structname>,
+   already exists, its parameters are updated according to the command,
    but the language's ownership and permissions settings do not change,
    and any existing functions written in the language are assumed to still
-   be valid.  In addition to the normal privilege requirements for creating
-   a language, the user must be superuser or owner of the existing language.
-   The <literal>REPLACE</literal> case is mainly meant to be used to
-   ensure that the language exists.  If the language has a
-   <structname>pg_pltemplate</structname> entry then <literal>REPLACE</literal>
-   will not actually change anything about an existing definition, except in
-   the unusual case where the <structname>pg_pltemplate</structname> entry
-   has been modified since the language was created.
+   be valid.
+  </para>
+
+  <para>
+   One must have the
+   <productname>PostgreSQL</productname> superuser privilege to
+   register a new language or change an existing language's parameters.
+   However, once the language is created it is valid to assign ownership of
+   it to a non-superuser, who may then drop it, change its permissions,
+   rename it, or assign it to a new owner.  (Do not, however, assign
+   ownership of the underlying C functions to a non-superuser; that would
+   create a privilege escalation path for that user.)
   </para>
  </refsect1>

@@ -218,12 +182,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
      </listitem>
     </varlistentry>
    </variablelist>
-
-  <para>
-   The <literal>TRUSTED</literal> option and the support function name(s) are
-   ignored if the server has an entry for the specified language
-   name in <structname>pg_pltemplate</structname>.
-  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-notes">
@@ -255,18 +213,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   The call handler function, the inline handler function (if any),
-   and the validator function (if any)
-   must already exist if the server does not have an entry for the language
-   in <structname>pg_pltemplate</structname>.  But when there is an entry,
-   the functions need not already exist;
-   they will be automatically defined if not present in the database.
-   (This might result in <command>CREATE LANGUAGE</command> failing, if the
-   shared library that implements the language is not available in
-   the installation.)
-  </para>
-
-  <para>
    In <productname>PostgreSQL</productname> versions before 7.3, it was
    necessary to declare handler functions as returning the placeholder
    type <type>opaque</type>, rather than <type>language_handler</type>.
@@ -281,23 +227,20 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   <title>Examples</title>

   <para>
-   The preferred way of creating any of the standard procedural languages
-   is just:
-<programlisting>
-CREATE LANGUAGE plperl;
-</programlisting>
-  </para>
-
-  <para>
-   For a language not known in the <structname>pg_pltemplate</structname> catalog, a
-   sequence such as this is needed:
+   A minimal sequence for creating a new procedural language is:
 <programlisting>
 CREATE FUNCTION plsample_call_handler() RETURNS language_handler
     AS '$libdir/plsample'
     LANGUAGE C;
 CREATE LANGUAGE plsample
     HANDLER plsample_call_handler;
-</programlisting></para>
+</programlisting>
+   Typically that would be written in an extension's creation script,
+   and users would do this to install the extension:
+<programlisting>
+CREATE EXTENSION plsample;
+</programlisting>
+  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-compat">
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index bf66a1c..21e6dd7 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2279,6 +2279,64 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 }

 /*
+ * Test whether the given extension exists (not whether it's installed)
+ *
+ * This checks for the existence of a matching control file in the extension
+ * directory.  That's not a bulletproof check, since the file might be
+ * invalid, but this is only used for hints so it doesn't have to be 100%
+ * right.
+ */
+bool
+extension_file_exists(const char *extensionName)
+{
+    bool        result = false;
+    char       *location;
+    DIR           *dir;
+    struct dirent *de;
+
+    location = get_extension_control_directory();
+    dir = AllocateDir(location);
+
+    /*
+     * If the control directory doesn't exist, we want to silently return
+     * false.  Any other error will be reported by ReadDir.
+     */
+    if (dir == NULL && errno == ENOENT)
+    {
+        /* do nothing */
+    }
+    else
+    {
+        while ((de = ReadDir(dir, location)) != NULL)
+        {
+            char       *extname;
+
+            if (!is_extension_control_filename(de->d_name))
+                continue;
+
+            /* extract extension name from 'name.control' filename */
+            extname = pstrdup(de->d_name);
+            *strrchr(extname, '.') = '\0';
+
+            /* ignore it if it's an auxiliary control file */
+            if (strstr(extname, "--"))
+                continue;
+
+            /* done if it matches request */
+            if (strcmp(extname, extensionName) == 0)
+            {
+                result = true;
+                break;
+            }
+        }
+
+        FreeDir(dir);
+    }
+
+    return result;
+}
+
+/*
  * Convert a list of extension names to a name[] Datum
  */
 static Datum
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 94411b5..286eb56 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/extension.h"
 #include "commands/proclang.h"
 #include "executor/execdesc.h"
 #include "executor/executor.h"
@@ -991,7 +992,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_OBJECT),
                  errmsg("language \"%s\" does not exist", language),
-                 (PLTemplateExists(language) ?
+                 (extension_file_exists(language) ?
                   errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

     languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
@@ -2225,7 +2226,7 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_OBJECT),
                  errmsg("language \"%s\" does not exist", language),
-                 (PLTemplateExists(language) ?
+                 (extension_file_exists(language) ?
                   errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

     languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index b51c373..ea9f559 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -13,329 +13,110 @@
  */
 #include "postgres.h"

-#include "access/genam.h"
-#include "access/htup_details.h"
 #include "access/table.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
-#include "catalog/pg_authid.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
-#include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/proclang.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
-#include "parser/parser.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
-#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"


-typedef struct
-{
-    bool        tmpltrusted;    /* trusted? */
-    bool        tmpldbacreate;    /* db owner allowed to create? */
-    char       *tmplhandler;    /* name of handler function */
-    char       *tmplinline;        /* name of anonymous-block handler, or NULL */
-    char       *tmplvalidator;    /* name of validator function, or NULL */
-    char       *tmpllibrary;    /* path of shared library */
-} PLTemplate;
-
-static ObjectAddress create_proc_lang(const char *languageName, bool replace,
-                                      Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                                      Oid valOid, bool trusted);
-static PLTemplate *find_language_template(const char *languageName);
-
 /*
  * CREATE LANGUAGE
  */
 ObjectAddress
 CreateProceduralLanguage(CreatePLangStmt *stmt)
 {
-    PLTemplate *pltemplate;
-    ObjectAddress tmpAddr;
+    const char *languageName = stmt->plname;
+    Oid            languageOwner = GetUserId();
     Oid            handlerOid,
                 inlineOid,
                 valOid;
     Oid            funcrettype;
     Oid            funcargtypes[1];
+    Relation    rel;
+    TupleDesc    tupDesc;
+    Datum        values[Natts_pg_language];
+    bool        nulls[Natts_pg_language];
+    bool        replaces[Natts_pg_language];
+    NameData    langname;
+    HeapTuple    oldtup;
+    HeapTuple    tup;
+    Oid            langoid;
+    bool        is_update;
+    ObjectAddress myself,
+                referenced;

     /*
-     * If we have template information for the language, ignore the supplied
-     * parameters (if any) and use the template information.
+     * Check permission
      */
-    if ((pltemplate = find_language_template(stmt->plname)) != NULL)
-    {
-        List       *funcname;
-
-        /*
-         * Give a notice if we are ignoring supplied parameters.
-         */
-        if (stmt->plhandler)
-            ereport(NOTICE,
-                    (errmsg("using pg_pltemplate information instead of CREATE LANGUAGE parameters")));
-
-        /*
-         * Check permission
-         */
-        if (!superuser())
-        {
-            if (!pltemplate->tmpldbacreate)
-                ereport(ERROR,
-                        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                         errmsg("must be superuser to create procedural language \"%s\"",
-                                stmt->plname)));
-            if (!pg_database_ownercheck(MyDatabaseId, GetUserId()))
-                aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
-                               get_database_name(MyDatabaseId));
-        }
-
-        /*
-         * Find or create the handler function, which we force to be in the
-         * pg_catalog schema.  If already present, it must have the correct
-         * return type.
-         */
-        funcname = SystemFuncName(pltemplate->tmplhandler);
-        handlerOid = LookupFuncName(funcname, 0, NULL, true);
-        if (OidIsValid(handlerOid))
-        {
-            funcrettype = get_func_rettype(handlerOid);
-            if (funcrettype != LANGUAGE_HANDLEROID)
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(funcname), "language_handler")));
-        }
-        else
-        {
-            tmpAddr = ProcedureCreate(pltemplate->tmplhandler,
-                                      PG_CATALOG_NAMESPACE,
-                                      false,    /* replace */
-                                      false,    /* returnsSet */
-                                      LANGUAGE_HANDLEROID,
-                                      BOOTSTRAP_SUPERUSERID,
-                                      ClanguageId,
-                                      F_FMGR_C_VALIDATOR,
-                                      pltemplate->tmplhandler,
-                                      pltemplate->tmpllibrary,
-                                      PROKIND_FUNCTION,
-                                      false,    /* security_definer */
-                                      false,    /* isLeakProof */
-                                      false,    /* isStrict */
-                                      PROVOLATILE_VOLATILE,
-                                      PROPARALLEL_UNSAFE,
-                                      buildoidvector(funcargtypes, 0),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      NIL,
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      InvalidOid,
-                                      1,
-                                      0);
-            handlerOid = tmpAddr.objectId;
-        }
-
-        /*
-         * Likewise for the anonymous block handler, if required; but we don't
-         * care about its return type.
-         */
-        if (pltemplate->tmplinline)
-        {
-            funcname = SystemFuncName(pltemplate->tmplinline);
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(inlineOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplinline,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplinline,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                inlineOid = tmpAddr.objectId;
-            }
-        }
-        else
-            inlineOid = InvalidOid;
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 errmsg("must be superuser to create custom procedural language")));

+    /*
+     * Lookup the PL handler function and check that it is of the expected
+     * return type
+     */
+    Assert(stmt->plhandler);
+    handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false);
+    funcrettype = get_func_rettype(handlerOid);
+    if (funcrettype != LANGUAGE_HANDLEROID)
+    {
         /*
-         * Likewise for the validator, if required; but we don't care about
-         * its return type.
+         * We allow OPAQUE just so we can load old dump files.  When we see a
+         * handler function declared OPAQUE, change it to LANGUAGE_HANDLER.
+         * (This is probably obsolete and removable?)
          */
-        if (pltemplate->tmplvalidator)
+        if (funcrettype == OPAQUEOID)
         {
-            funcname = SystemFuncName(pltemplate->tmplvalidator);
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(valOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplvalidator,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplvalidator,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                valOid = tmpAddr.objectId;
-            }
+            ereport(WARNING,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("changing return type of function %s from %s to %s",
+                            NameListToString(stmt->plhandler),
+                            "opaque", "language_handler")));
+            SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
         }
         else
-            valOid = InvalidOid;
+            ereport(ERROR,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("function %s must return type %s",
+                            NameListToString(stmt->plhandler), "language_handler")));
+    }

-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, pltemplate->tmpltrusted);
+    /* validate the inline function */
+    if (stmt->plinline)
+    {
+        funcargtypes[0] = INTERNALOID;
+        inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
     else
-    {
-        /*
-         * No template, so use the provided information.  If there's no
-         * handler clause, the user is trying to rely on a template that we
-         * don't have, so complain accordingly.
-         */
-        if (!stmt->plhandler)
-            ereport(ERROR,
-                    (errcode(ERRCODE_UNDEFINED_OBJECT),
-                     errmsg("unsupported language \"%s\"",
-                            stmt->plname),
-                     errhint("The supported languages are listed in the pg_pltemplate system catalog.")));
+        inlineOid = InvalidOid;

-        /*
-         * Check permission
-         */
-        if (!superuser())
-            ereport(ERROR,
-                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                     errmsg("must be superuser to create custom procedural language")));
-
-        /*
-         * Lookup the PL handler function and check that it is of the expected
-         * return type
-         */
-        handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false);
-        funcrettype = get_func_rettype(handlerOid);
-        if (funcrettype != LANGUAGE_HANDLEROID)
-        {
-            /*
-             * We allow OPAQUE just so we can load old dump files.  When we
-             * see a handler function declared OPAQUE, change it to
-             * LANGUAGE_HANDLER.  (This is probably obsolete and removable?)
-             */
-            if (funcrettype == OPAQUEOID)
-            {
-                ereport(WARNING,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("changing return type of function %s from %s to %s",
-                                NameListToString(stmt->plhandler),
-                                "opaque", "language_handler")));
-                SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
-            }
-            else
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(stmt->plhandler), "language_handler")));
-        }
-
-        /* validate the inline function */
-        if (stmt->plinline)
-        {
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            inlineOid = InvalidOid;
-
-        /* validate the validator function */
-        if (stmt->plvalidator)
-        {
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            valOid = InvalidOid;
-
-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, stmt->pltrusted);
+    /* validate the validator function */
+    if (stmt->plvalidator)
+    {
+        funcargtypes[0] = OIDOID;
+        valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
-}
-
-/*
- * Guts of language creation.
- */
-static ObjectAddress
-create_proc_lang(const char *languageName, bool replace,
-                 Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                 Oid valOid, bool trusted)
-{
-    Relation    rel;
-    TupleDesc    tupDesc;
-    Datum        values[Natts_pg_language];
-    bool        nulls[Natts_pg_language];
-    bool        replaces[Natts_pg_language];
-    NameData    langname;
-    HeapTuple    oldtup;
-    HeapTuple    tup;
-    Oid            langoid;
-    bool        is_update;
-    ObjectAddress myself,
-                referenced;
+    else
+        valOid = InvalidOid;

+    /* ok to create it */
     rel = table_open(LanguageRelationId, RowExclusiveLock);
     tupDesc = RelationGetDescr(rel);

@@ -348,7 +129,7 @@ create_proc_lang(const char *languageName, bool replace,
     values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname);
     values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner);
     values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true);
-    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(trusted);
+    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(stmt->pltrusted);
     values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
     values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
     values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
@@ -362,13 +143,17 @@ create_proc_lang(const char *languageName, bool replace,
         Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup);

         /* There is one; okay to replace it? */
-        if (!replace)
+        if (!stmt->replace)
             ereport(ERROR,
                     (errcode(ERRCODE_DUPLICATE_OBJECT),
                      errmsg("language \"%s\" already exists", languageName)));
+
+        /* This is currently pointless, since we already checked superuser */
+#ifdef NOT_USED
         if (!pg_language_ownercheck(oldform->oid, languageOwner))
             aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE,
                            languageName);
+#endif

         /*
          * Do not change existing oid, ownership or permissions.  Note
@@ -451,83 +236,6 @@ create_proc_lang(const char *languageName, bool replace,
 }

 /*
- * Look to see if we have template information for the given language name.
- */
-static PLTemplate *
-find_language_template(const char *languageName)
-{
-    PLTemplate *result;
-    Relation    rel;
-    SysScanDesc scan;
-    ScanKeyData key;
-    HeapTuple    tup;
-
-    rel = table_open(PLTemplateRelationId, AccessShareLock);
-
-    ScanKeyInit(&key,
-                Anum_pg_pltemplate_tmplname,
-                BTEqualStrategyNumber, F_NAMEEQ,
-                CStringGetDatum(languageName));
-    scan = systable_beginscan(rel, PLTemplateNameIndexId, true,
-                              NULL, 1, &key);
-
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_pltemplate tmpl = (Form_pg_pltemplate) GETSTRUCT(tup);
-        Datum        datum;
-        bool        isnull;
-
-        result = (PLTemplate *) palloc0(sizeof(PLTemplate));
-        result->tmpltrusted = tmpl->tmpltrusted;
-        result->tmpldbacreate = tmpl->tmpldbacreate;
-
-        /* Remaining fields are variable-width so we need heap_getattr */
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplhandler,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplhandler = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplinline,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplinline = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplvalidator,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplvalidator = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmpllibrary = TextDatumGetCString(datum);
-
-        /* Ignore template if handler or library info is missing */
-        if (!result->tmplhandler || !result->tmpllibrary)
-            result = NULL;
-    }
-    else
-        result = NULL;
-
-    systable_endscan(scan);
-
-    table_close(rel, AccessShareLock);
-
-    return result;
-}
-
-
-/*
- * This just returns true if we have a valid template for a given language
- */
-bool
-PLTemplateExists(const char *languageName)
-{
-    return (find_language_template(languageName) != NULL);
-}
-
-/*
  * Guts of language dropping.
  */
 void
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2f7bd66..c8e3a24 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4304,14 +4304,17 @@ NumericOnly_list:    NumericOnly                        { $$ = list_make1($1); }
 CreatePLangStmt:
             CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
             {
-                CreatePLangStmt *n = makeNode(CreatePLangStmt);
-                n->replace = $2;
-                n->plname = $6;
-                /* parameters are all to be supplied by system */
-                n->plhandler = NIL;
-                n->plinline = NIL;
-                n->plvalidator = NIL;
-                n->pltrusted = false;
+                /*
+                 * We now interpret parameterless CREATE LANGUAGE as
+                 * CREATE EXTENSION.  "OR REPLACE" is silently translated
+                 * to "IF NOT EXISTS", which isn't quite the same, but
+                 * seems more useful than throwing an error.  We just
+                 * ignore TRUSTED, as the previous code would have too.
+                 */
+                CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
+                n->if_not_exists = $2;
+                n->extname = $6;
+                n->options = NIL;
                 $$ = (Node *)n;
             }
             | CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 02fc17d..a6a27a6 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -47,6 +47,7 @@ extern ObjectAddress ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *

 extern Oid    get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
+extern bool extension_file_exists(const char *extensionName);

 extern ObjectAddress AlterExtensionNamespace(const char *extensionName, const char *newschema,
                                              Oid *oldschema);
diff --git a/src/include/commands/proclang.h b/src/include/commands/proclang.h
index 9a4bc75..d7b0ab5 100644
--- a/src/include/commands/proclang.h
+++ b/src/include/commands/proclang.h
@@ -1,11 +1,12 @@
-/*
- * src/include/commands/proclang.h
- *
- *-------------------------------------------------------------------------
+/*-------------------------------------------------------------------------
  *
  * proclang.h
  *      prototypes for proclang.c.
  *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/proclang.h
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +18,7 @@

 extern ObjectAddress CreateProceduralLanguage(CreatePLangStmt *stmt);
 extern void DropProceduralLanguageById(Oid langOid);
-extern bool PLTemplateExists(const char *languageName);
+
 extern Oid    get_language_oid(const char *langname, bool missing_ok);

 #endif                            /* PROCLANG_H */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index a573dfb..03ad677 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,11 +226,6 @@
      </row>

      <row>
-      <entry><link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link></entry>
-      <entry>template data for procedural languages</entry>
-     </row>
-
-     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -4911,113 +4906,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
  </sect1>


- <sect1 id="catalog-pg-pltemplate">
-  <title><structname>pg_pltemplate</structname></title>
-
-  <indexterm zone="catalog-pg-pltemplate">
-   <primary>pg_pltemplate</primary>
-  </indexterm>
-
-  <para>
-   The catalog <structname>pg_pltemplate</structname> stores
-   <quote>template</quote> information for procedural languages.
-   A template for a language allows the language to be created in a
-   particular database by a simple <command>CREATE LANGUAGE</command> command,
-   with no need to specify implementation details.
-  </para>
-
-  <para>
-   Unlike most system catalogs, <structname>pg_pltemplate</structname>
-   is shared across all databases of a cluster: there is only one
-   copy of <structname>pg_pltemplate</structname> per cluster, not
-   one per database.  This allows the information to be accessible in
-   each database as it is needed.
-  </para>
-
-  <table>
-   <title><structname>pg_pltemplate</structname> Columns</title>
-
-   <tgroup cols="3">
-    <thead>
-     <row>
-      <entry>Name</entry>
-      <entry>Type</entry>
-      <entry>Description</entry>
-     </row>
-    </thead>
-
-    <tbody>
-     <row>
-      <entry><structfield>tmplname</structfield></entry>
-      <entry><type>name</type></entry>
-      <entry>Name of the language this template is for</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpltrusted</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language is considered trusted</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpldbacreate</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language may be created by a database owner</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplhandler</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of call handler function</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplinline</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of anonymous-block handler function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplvalidator</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of validator function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpllibrary</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Path of shared library that implements language</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplacl</structfield></entry>
-      <entry><type>aclitem[]</type></entry>
-      <entry>Access privileges for template (not actually used)</entry>
-     </row>
-
-    </tbody>
-   </tgroup>
-  </table>
-
-  <para>
-   There are not currently any commands that manipulate procedural language
-   templates; to change the built-in information, a superuser must modify
-   the table using ordinary <command>INSERT</command>, <command>DELETE</command>,
-   or <command>UPDATE</command> commands.
-  </para>
-
-  <note>
-   <para>
-    It is likely that <structname>pg_pltemplate</structname> will be removed in some
-    future release of <productname>PostgreSQL</productname>, in favor of
-    keeping this knowledge about procedural languages in their respective
-    extension installation scripts.
-   </para>
-  </note>
-
- </sect1>
-
-
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>

diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 4c2f7e8..388a774 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -153,7 +153,7 @@
      <para>
       Daredevils, who want to build a Python-3-only operating system
       environment, can change the contents of
-      <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
+      <literal>plpythonu</literal>'s extension control and script files
       to make <literal>plpythonu</literal> be equivalent
       to <literal>plpython3u</literal>, keeping in mind that this
       would make their installation incompatible with most of the rest
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a511532..1451b78 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -60,7 +60,7 @@ CATALOG_HEADERS := \
     pg_statistic_ext.h pg_statistic_ext_data.h \
     pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
     pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
-    pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
+    pg_database.h pg_db_role_setting.h pg_tablespace.h \
     pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
     pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
     pg_ts_parser.h pg_ts_template.h pg_extension.h \
@@ -86,7 +86,7 @@ POSTGRES_BKI_DATA = $(addprefix $(top_srcdir)/src/include/catalog/,\
     pg_cast.dat pg_class.dat pg_collation.dat pg_conversion.dat \
     pg_database.dat pg_language.dat \
     pg_namespace.dat pg_opclass.dat pg_operator.dat pg_opfamily.dat \
-    pg_pltemplate.dat pg_proc.dat pg_range.dat pg_tablespace.dat \
+    pg_proc.dat pg_range.dat pg_tablespace.dat \
     pg_ts_config.dat pg_ts_config_map.dat pg_ts_dict.dat pg_ts_parser.dat \
     pg_ts_template.dat pg_type.dat \
     )
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 6b10469..57c6247 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -33,7 +33,6 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
 #include "catalog/pg_shdescription.h"
@@ -242,7 +241,6 @@ IsSharedRelation(Oid relationId)
     if (relationId == AuthIdRelationId ||
         relationId == AuthMemRelationId ||
         relationId == DatabaseRelationId ||
-        relationId == PLTemplateRelationId ||
         relationId == SharedDescriptionRelationId ||
         relationId == SharedDependRelationId ||
         relationId == SharedSecLabelRelationId ||
@@ -258,7 +256,6 @@ IsSharedRelation(Oid relationId)
         relationId == AuthMemMemRoleIndexId ||
         relationId == DatabaseNameIndexId ||
         relationId == DatabaseOidIndexId ||
-        relationId == PLTemplateNameIndexId ||
         relationId == SharedDescriptionObjIndexId ||
         relationId == SharedDependDependerIndexId ||
         relationId == SharedDependReferenceIndexId ||
@@ -278,8 +275,6 @@ IsSharedRelation(Oid relationId)
         relationId == PgDatabaseToastIndex ||
         relationId == PgDbRoleSettingToastTable ||
         relationId == PgDbRoleSettingToastIndex ||
-        relationId == PgPlTemplateToastTable ||
-        relationId == PgPlTemplateToastIndex ||
         relationId == PgReplicationOriginToastTable ||
         relationId == PgReplicationOriginToastIndex ||
         relationId == PgShdescriptionToastTable ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bf69adc..6b0fd42 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11396,9 +11396,9 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
     }

     /*
-     * If the functions are dumpable then emit a traditional CREATE LANGUAGE
-     * with parameters.  Otherwise, we'll write a parameterless command, which
-     * will rely on data from pg_pltemplate.
+     * If the functions are dumpable then emit a complete CREATE LANGUAGE with
+     * parameters.  Otherwise, we'll write a parameterless command, which will
+     * be interpreted as CREATE EXTENSION.
      */
     useParams = (funcInfo != NULL &&
                  (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
@@ -11431,11 +11431,11 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
         /*
          * If not dumping parameters, then use CREATE OR REPLACE so that the
          * command will not fail if the language is preinstalled in the target
-         * database.  We restrict the use of REPLACE to this case so as to
-         * eliminate the risk of replacing a language with incompatible
-         * parameter settings: this command will only succeed at all if there
-         * is a pg_pltemplate entry, and if there is one, the existing entry
-         * must match it too.
+         * database.
+         *
+         * Modern servers will interpret this as CREATE EXTENSION IF NOT
+         * EXISTS; perhaps we should emit that instead?  But it might just add
+         * confusion.
          */
         appendPQExpBuffer(defqry, "CREATE OR REPLACE PROCEDURAL LANGUAGE %s",
                           qlanname);
diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c
index ac984db..a239c53 100644
--- a/src/bin/pg_upgrade/function.c
+++ b/src/bin/pg_upgrade/function.c
@@ -214,13 +214,9 @@ check_loadable_libraries(void)
              * plpython2u language was created with library name plpython2.so
              * as a symbolic link to plpython.so.  In Postgres 9.1, only the
              * plpython2.so library was created, and both plpythonu and
-             * plpython2u pointing to it.  For this reason, any reference to
+             * plpython2u point to it.  For this reason, any reference to
              * library name "plpython" in an old PG <= 9.1 cluster must look
              * for "plpython2" in the new cluster.
-             *
-             * For this case, we could check pg_pltemplate, but that only
-             * works for languages, and does not help with function shared
-             * objects, so we just do a general fix.
              */
             if (GET_MAJOR_VERSION(old_cluster.major_version) < 901 &&
                 strcmp(lib, "$libdir/plpython") == 0)
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ef4445b..7294d59 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -206,9 +206,6 @@ DECLARE_UNIQUE_INDEX(pg_opfamily_am_name_nsp_index, 2754, on pg_opfamily using b
 DECLARE_UNIQUE_INDEX(pg_opfamily_oid_index, 2755, on pg_opfamily using btree(oid oid_ops));
 #define OpfamilyOidIndexId    2755

-DECLARE_UNIQUE_INDEX(pg_pltemplate_name_index, 1137, on pg_pltemplate using btree(tmplname name_ops));
-#define PLTemplateNameIndexId  1137
-
 DECLARE_UNIQUE_INDEX(pg_proc_oid_index, 2690, on pg_proc using btree(oid oid_ops));
 #define ProcedureOidIndexId  2690
 DECLARE_UNIQUE_INDEX(pg_proc_proname_args_nsp_index, 2691, on pg_proc using btree(proname name_ops, proargtypes
oidvector_ops,pronamespace oid_ops)); 
diff --git a/src/include/catalog/pg_pltemplate.dat b/src/include/catalog/pg_pltemplate.dat
deleted file mode 100644
index 6f6d167..0000000
--- a/src/include/catalog/pg_pltemplate.dat
+++ /dev/null
@@ -1,51 +0,0 @@
-#----------------------------------------------------------------------
-#
-# pg_pltemplate.dat
-#    Initial contents of the pg_pltemplate system catalog.
-#
-# Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
-# Portions Copyright (c) 1994, Regents of the University of California
-#
-# src/include/catalog/pg_pltemplate.dat
-#
-#----------------------------------------------------------------------
-
-[
-
-{ tmplname => 'plpgsql', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plpgsql_call_handler', tmplinline => 'plpgsql_inline_handler',
-  tmplvalidator => 'plpgsql_validator', tmpllibrary => '$libdir/plpgsql',
-  tmplacl => '_null_' },
-{ tmplname => 'pltcl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'pltcl_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'pltclu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'pltclu_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plperl_call_handler', tmplinline => 'plperl_inline_handler',
-  tmplvalidator => 'plperl_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperlu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plperlu_call_handler', tmplinline => 'plperlu_inline_handler',
-  tmplvalidator => 'plperlu_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plpythonu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython_call_handler',
-  tmplinline => 'plpython_inline_handler',
-  tmplvalidator => 'plpython_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython2u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython2_call_handler',
-  tmplinline => 'plpython2_inline_handler',
-  tmplvalidator => 'plpython2_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython3u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython3_call_handler',
-  tmplinline => 'plpython3_inline_handler',
-  tmplvalidator => 'plpython3_validator', tmpllibrary => '$libdir/plpython3',
-  tmplacl => '_null_' },
-
-]
diff --git a/src/include/catalog/pg_pltemplate.h b/src/include/catalog/pg_pltemplate.h
deleted file mode 100644
index ce89000..0000000
--- a/src/include/catalog/pg_pltemplate.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * pg_pltemplate.h
- *      definition of the "PL template" system catalog (pg_pltemplate)
- *
- *
- * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/catalog/pg_pltemplate.h
- *
- * NOTES
- *      The Catalog.pm module reads this file and derives schema
- *      information.
- *
- *-------------------------------------------------------------------------
- */
-#ifndef PG_PLTEMPLATE_H
-#define PG_PLTEMPLATE_H
-
-#include "catalog/genbki.h"
-#include "catalog/pg_pltemplate_d.h"
-
-/* ----------------
- *        pg_pltemplate definition.  cpp turns this into
- *        typedef struct FormData_pg_pltemplate
- * ----------------
- */
-CATALOG(pg_pltemplate,1136,PLTemplateRelationId) BKI_SHARED_RELATION
-{
-    NameData    tmplname;        /* name of PL */
-    bool        tmpltrusted;    /* PL is trusted? */
-    bool        tmpldbacreate;    /* PL is installable by db owner? */
-
-#ifdef CATALOG_VARLEN            /* variable-length fields start here */
-    text        tmplhandler BKI_FORCE_NOT_NULL; /* name of call handler
-                                                 * function */
-    text        tmplinline;        /* name of anonymous-block handler, or NULL */
-    text        tmplvalidator;    /* name of validator function, or NULL */
-    text        tmpllibrary BKI_FORCE_NOT_NULL; /* path of shared library */
-    aclitem        tmplacl[1];        /* access privileges for template */
-#endif
-} FormData_pg_pltemplate;
-
-/* ----------------
- *        Form_pg_pltemplate corresponds to a pointer to a row with
- *        the format of pg_pltemplate relation.
- * ----------------
- */
-typedef FormData_pg_pltemplate *Form_pg_pltemplate;
-
-#endif                            /* PG_PLTEMPLATE_H */
diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h
index cc5dfed..5a047fc 100644
--- a/src/include/catalog/toasting.h
+++ b/src/include/catalog/toasting.h
@@ -86,9 +86,6 @@ DECLARE_TOAST(pg_database, 4177, 4178);
 DECLARE_TOAST(pg_db_role_setting, 2966, 2967);
 #define PgDbRoleSettingToastTable 2966
 #define PgDbRoleSettingToastIndex 2967
-DECLARE_TOAST(pg_pltemplate, 4179, 4180);
-#define PgPlTemplateToastTable 4179
-#define PgPlTemplateToastIndex 4180
 DECLARE_TOAST(pg_replication_origin, 4181, 4182);
 #define PgReplicationOriginToastTable 4181
 #define PgReplicationOriginToastIndex 4182
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 6edfcf2..0d9564e 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -33,7 +33,7 @@
  */

 #if PY_MAJOR_VERSION >= 3
-/* Use separate names to avoid clash in pg_pltemplate */
+/* Use separate names to reduce confusion */
 #define plpython_validator plpython3_validator
 #define plpython_call_handler plpython3_call_handler
 #define plpython_inline_handler plpython3_inline_handler
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 070de78..f0cd40b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -134,7 +134,6 @@ pg_opclass|t
 pg_operator|t
 pg_opfamily|t
 pg_partitioned_table|t
-pg_pltemplate|t
 pg_policy|t
 pg_proc|t
 pg_publication|t

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
I wrote:
> The cfbot noticed that a couple of patches committed this week
> created (trivial) conflicts with this patchset.  Here's a v3
> rebased up to HEAD; no interesting changes.

The 2020 copyright update broke this patchset again.  Here's a rebase.
No changes except for some minor rearrangement of the CREATE LANGUAGE
man page in 0003.

            regards, tom lane

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 55694c4..a573dfb 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8514,7 +8514,15 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
      <row>
       <entry><structfield>superuser</structfield></entry>
       <entry><type>bool</type></entry>
-      <entry>True if only superusers are allowed to install this extension</entry>
+      <entry>True if only superusers are allowed to install this extension
+       (but see <structfield>trusted</structfield>)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>trusted</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry>True if the extension can be installed by non-superusers
+       with appropriate privileges</entry>
      </row>

      <row>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f2..e2807d0 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -576,6 +576,32 @@
         version.  If it is set to <literal>false</literal>, just the privileges
         required to execute the commands in the installation or update script
         are required.
+        This should normally be set to <literal>true</literal> if any of the
+        script commands require superuser privileges.  (Such commands would
+        fail anyway, but it's more user-friendly to give the error up front.)
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>trusted</varname> (<type>boolean</type>)</term>
+      <listitem>
+       <para>
+        This parameter, if set to <literal>true</literal> (which is not the
+        default), allows some non-superusers to install an extension that
+        has <varname>superuser</varname> set to <literal>true</literal>.
+        Specifically, installation will be permitted for the owner of the
+        current database, and for anyone who has been granted
+        the <literal>pg_install_trusted_extension</literal> role.
+        When the user executing <command>CREATE EXTENSION</command> is not
+        a superuser but is allowed to install by virtue of this parameter,
+        then the installation or update script is run as the bootstrap
+        superuser, not as the calling user.
+        This parameter is irrelevant if <varname>superuser</varname> is
+        <literal>false</literal>.
+        Generally, this should not be set true for extensions that could
+        allow access to otherwise-superuser-only abilities, such as
+        filesystem access.
        </para>
       </listitem>
      </varlistentry>
@@ -642,6 +668,18 @@
     </para>

     <para>
+     If the extension script contains the
+     string <literal>@extowner@</literal>, that string is replaced with the
+     (suitably quoted) name of the user calling <command>CREATE
+     EXTENSION</command> or <command>ALTER EXTENSION</command>.  Typically
+     this feature is used by extensions that are marked trusted to assign
+     ownership of selected objects to the calling user rather than the
+     bootstrap superuser.  (One should be careful about doing so, however.
+     For example, assigning ownership of a C-language function to a
+     non-superuser would create a privilege escalation path for that user.)
+    </para>
+
+    <para>
      While the script files can contain any characters allowed by the specified
      encoding, control files should contain only plain ASCII, because there
      is no way for <productname>PostgreSQL</productname> to know what encoding a
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index 36837f9..7cd4346 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -47,14 +47,26 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
   </para>

   <para>
-   Loading an extension requires the same privileges that would be
-   required to create its component objects.  For most extensions this
-   means superuser or database owner privileges are needed.
    The user who runs <command>CREATE EXTENSION</command> becomes the
    owner of the extension for purposes of later privilege checks, as well
    as the owner of any objects created by the extension's script.
   </para>

+  <para>
+   Loading an extension ordinarily requires the same privileges that would
+   be required to create its component objects.  For many extensions this
+   means superuser privileges are needed.
+   However, if the extension is marked <firstterm>trusted</firstterm> in
+   its control file, then it can be installed by a non-superuser who has
+   suitable privileges (that is, owns the current database or has been
+   granted the <literal>pg_install_trusted_extension</literal> role).  In
+   this case the extension object itself will be owned by the calling user,
+   but the contained objects will be owned by the bootstrap superuser
+   (unless the extension's script explicitly assigns them to the calling
+   user).  This configuration gives the calling user the right to drop the
+   extension, but not to modify individual objects within it.
+  </para>
+
  </refsect1>

  <refsect1>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 66f1627..90f637f 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -556,6 +556,10 @@ DROP ROLE doomed_role;
        <entry>Allow executing programs on the database server as the user the database runs as with
        COPY and other functions which allow executing a server-side program.</entry>
       </row>
+      <row>
+       <entry>pg_install_trusted_extension</entry>
+       <entry>Allow installation of <quote>trusted</quote> extensions.</entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
@@ -589,6 +593,17 @@ DROP ROLE doomed_role;
   </para>

   <para>
+  The <literal>pg_install_trusted_extension</literal> role allows grantees
+  to install <link linkend="extend-extensions">extensions</link> that are
+  marked <quote>trusted</quote> in their control files.  This is a
+  privilege that is available automatically to database owners, but
+  granting this role allows administrators to let other non-superuser roles
+  do it too.  Generally, unless the <quote>trusted</quote> marking has been
+  applied to extensions incautiously, granting this role carries no large
+  security risk.
+  </para>
+
+  <para>
   Care should be taken when granting these roles to ensure they are only used where
   needed and with the understanding that these roles grant access to privileged
   information.
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2fc3e3f..12619d1 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -317,7 +317,8 @@ CREATE VIEW pg_available_extensions AS

 CREATE VIEW pg_available_extension_versions AS
     SELECT E.name, E.version, (X.extname IS NOT NULL) AS installed,
-           E.superuser, E.relocatable, E.schema, E.requires, E.comment
+           E.superuser, E.trusted, E.relocatable,
+           E.schema, E.requires, E.comment
       FROM pg_available_extension_versions() AS E
            LEFT JOIN pg_extension AS X
              ON E.name = X.extname AND E.version = X.extversion;
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 01de398..339ba5b 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -40,6 +40,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
@@ -84,6 +85,7 @@ typedef struct ExtensionControlFile
     char       *schema;            /* target schema (allowed if !relocatable) */
     bool        relocatable;    /* is ALTER EXTENSION SET SCHEMA supported? */
     bool        superuser;        /* must be superuser to install? */
+    bool        trusted;        /* allow becoming superuser on the fly? */
     int            encoding;        /* encoding of the script file, or -1 */
     List       *requires;        /* names of prerequisite extensions */
 } ExtensionControlFile;
@@ -558,6 +560,14 @@ parse_extension_control_file(ExtensionControlFile *control,
                          errmsg("parameter \"%s\" requires a Boolean value",
                                 item->name)));
         }
+        else if (strcmp(item->name, "trusted") == 0)
+        {
+            if (!parse_bool(item->value, &control->trusted))
+                ereport(ERROR,
+                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                         errmsg("parameter \"%s\" requires a Boolean value",
+                                item->name)));
+        }
         else if (strcmp(item->name, "encoding") == 0)
         {
             control->encoding = pg_valid_server_encoding(item->value);
@@ -614,6 +624,7 @@ read_extension_control_file(const char *extname)
     control->name = pstrdup(extname);
     control->relocatable = false;
     control->superuser = true;
+    control->trusted = false;
     control->encoding = -1;

     /*
@@ -795,6 +806,27 @@ execute_sql_string(const char *sql)
 }

 /*
+ * Policy function: is the given extension trusted for installation by a
+ * non-superuser?
+ *
+ * (Update the errhint logic below if you change this.)
+ */
+static bool
+extension_is_trusted(ExtensionControlFile *control)
+{
+    /* Never trust unless extension's control file says it's okay */
+    if (!control->trusted)
+        return false;
+    /* Database owner can install */
+    if (pg_database_ownercheck(MyDatabaseId, GetUserId()))
+        return true;
+    /* Members of pg_install_trusted_extension can install */
+    if (has_privs_of_role(GetUserId(), DEFAULT_ROLE_INSTALL_TRUSTED_EXTENSION))
+        return true;
+    return false;
+}
+
+/*
  * Execute the appropriate script file for installing or updating the extension
  *
  * If from_version isn't NULL, it's an update
@@ -806,35 +838,56 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                          List *requiredSchemas,
                          const char *schemaName, Oid schemaOid)
 {
+    bool        switch_to_superuser = false;
     char       *filename;
+    Oid            save_userid = 0;
+    int            save_sec_context = 0;
     int            save_nestlevel;
     StringInfoData pathbuf;
     ListCell   *lc;

     /*
-     * Enforce superuser-ness if appropriate.  We postpone this check until
-     * here so that the flag is correctly associated with the right script(s)
-     * if it's set in secondary control files.
+     * Enforce superuser-ness if appropriate.  We postpone these checks until
+     * here so that the control flags are correctly associated with the right
+     * script(s) if they happen to be set in secondary control files.
      */
     if (control->superuser && !superuser())
     {
-        if (from_version == NULL)
+        if (extension_is_trusted(control))
+            switch_to_superuser = true;
+        else if (from_version == NULL)
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("permission denied to create extension \"%s\"",
                             control->name),
-                     errhint("Must be superuser to create this extension.")));
+                     control->trusted
+                     ? errhint("Must be database owner or member of pg_install_trusted_extension to create this
extension.")
+                     : errhint("Must be superuser to create this extension.")));
         else
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("permission denied to update extension \"%s\"",
                             control->name),
-                     errhint("Must be superuser to update this extension.")));
+                     control->trusted
+                     ? errhint("Must be database owner or member of pg_install_trusted_extension to update this
extension.")
+                     : errhint("Must be superuser to update this extension.")));
     }

     filename = get_extension_script_filename(control, from_version, version);

     /*
+     * If installing a trusted extension on behalf of a non-superuser, become
+     * the bootstrap superuser.  (This switch will be cleaned up automatically
+     * if the transaction aborts, as will the GUC changes below.)
+     */
+    if (switch_to_superuser)
+    {
+        GetUserIdAndSecContext(&save_userid, &save_sec_context);
+        SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
+                               save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+    }
+
+    /*
      * 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.
@@ -907,6 +960,22 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                                         CStringGetTextDatum("ng"));

         /*
+         * If the script uses @extowner@, substitute the calling username.
+         */
+        if (strstr(c_sql, "@extowner@"))
+        {
+            Oid            uid = switch_to_superuser ? save_userid : GetUserId();
+            const char *userName = GetUserNameFromId(uid, false);
+            const char *qUserName = quote_identifier(userName);
+
+            t_sql = DirectFunctionCall3Coll(replace_text,
+                                            C_COLLATION_OID,
+                                            t_sql,
+                                            CStringGetTextDatum("@extowner@"),
+                                            CStringGetTextDatum(qUserName));
+        }
+
+        /*
          * If it's not relocatable, substitute the target schema name for
          * occurrences of @extschema@.
          *
@@ -953,6 +1022,12 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
      * Restore the GUC variables we set above.
      */
     AtEOXact_GUC(true, save_nestlevel);
+
+    /*
+     * Restore authentication state if needed.
+     */
+    if (switch_to_superuser)
+        SetUserIdAndSecContext(save_userid, save_sec_context);
 }

 /*
@@ -2111,8 +2186,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
     {
         ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
         ExtensionControlFile *control;
-        Datum        values[7];
-        bool        nulls[7];
+        Datum        values[8];
+        bool        nulls[8];
         ListCell   *lc2;

         if (!evi->installable)
@@ -2133,24 +2208,26 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
         values[1] = CStringGetTextDatum(evi->name);
         /* superuser */
         values[2] = BoolGetDatum(control->superuser);
+        /* trusted */
+        values[3] = BoolGetDatum(control->trusted);
         /* relocatable */
-        values[3] = BoolGetDatum(control->relocatable);
+        values[4] = BoolGetDatum(control->relocatable);
         /* schema */
         if (control->schema == NULL)
-            nulls[4] = true;
+            nulls[5] = true;
         else
-            values[4] = DirectFunctionCall1(namein,
+            values[5] = DirectFunctionCall1(namein,
                                             CStringGetDatum(control->schema));
         /* requires */
         if (control->requires == NIL)
-            nulls[5] = true;
+            nulls[6] = true;
         else
-            values[5] = convert_requires_to_datum(control->requires);
+            values[6] = convert_requires_to_datum(control->requires);
         /* comment */
         if (control->comment == NULL)
-            nulls[6] = true;
+            nulls[7] = true;
         else
-            values[6] = CStringGetTextDatum(control->comment);
+            values[7] = CStringGetTextDatum(control->comment);

         tuplestore_putvalues(tupstore, tupdesc, values, nulls);

@@ -2178,16 +2255,18 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
                 values[1] = CStringGetTextDatum(evi2->name);
                 /* superuser */
                 values[2] = BoolGetDatum(control->superuser);
+                /* trusted */
+                values[3] = BoolGetDatum(control->trusted);
                 /* relocatable */
-                values[3] = BoolGetDatum(control->relocatable);
+                values[4] = BoolGetDatum(control->relocatable);
                 /* schema stays the same */
                 /* requires */
                 if (control->requires == NIL)
-                    nulls[5] = true;
+                    nulls[6] = true;
                 else
                 {
-                    values[5] = convert_requires_to_datum(control->requires);
-                    nulls[5] = false;
+                    values[6] = convert_requires_to_datum(control->requires);
+                    nulls[6] = false;
                 }
                 /* comment stays the same */

diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 7c08851..a0df297 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -60,5 +60,10 @@
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '9495', oid_symbol => 'DEFAULT_ROLE_INSTALL_TRUSTED_EXTENSION',
+  rolname => 'pg_install_trusted_extension', rolsuper => 'f', rolinherit => 't',
+  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+  rolpassword => '_null_', rolvaliduntil => '_null_' },

 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0b6045a..d394e8d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9457,9 +9457,9 @@
   proname => 'pg_available_extension_versions', procost => '10',
   prorows => '100', proretset => 't', provolatile => 's',
   prorettype => 'record', proargtypes => '',
-  proallargtypes => '{name,text,bool,bool,name,_name,text}',
-  proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,version,superuser,relocatable,schema,requires,comment}',
+  proallargtypes => '{name,text,bool,bool,bool,name,_name,text}',
+  proargmodes => '{o,o,o,o,o,o,o,o}',
+  proargnames => '{name,version,superuser,trusted,relocatable,schema,requires,comment}',
   prosrc => 'pg_available_extension_versions' },
 { oid => '3084', descr => 'list an extension\'s version update paths',
   proname => 'pg_extension_update_paths', procost => '10', prorows => '100',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80a0782..adb2070 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1311,11 +1311,12 @@ pg_available_extension_versions| SELECT e.name,
     e.version,
     (x.extname IS NOT NULL) AS installed,
     e.superuser,
+    e.trusted,
     e.relocatable,
     e.schema,
     e.requires,
     e.comment
-   FROM (pg_available_extension_versions() e(name, version, superuser, relocatable, schema, requires, comment)
+   FROM (pg_available_extension_versions() e(name, version, superuser, trusted, relocatable, schema, requires,
comment)
      LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion))));
 pg_available_extensions| SELECT e.name,
     e.default_version,
diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile
index 9b1c514..e4d0a0b 100644
--- a/src/pl/plperl/GNUmakefile
+++ b/src/pl/plperl/GNUmakefile
@@ -55,8 +55,10 @@ endif # win32

 SHLIB_LINK = $(perl_embed_ldflags)

-REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-extension=plperl  --load-extension=plperlu
-REGRESS = plperl plperl_lc plperl_trigger plperl_shared plperl_elog plperl_util plperl_init plperlu plperl_array
plperl_callplperl_transaction 
+REGRESS_OPTS = --dbname=$(PL_TESTDB)
+REGRESS = plperl_setup plperl plperl_lc plperl_trigger plperl_shared \
+    plperl_elog plperl_util plperl_init plperlu plperl_array \
+    plperl_call plperl_transaction
 # if Perl can support two interpreters in one backend,
 # test plperl-and-plperlu cases
 ifneq ($(PERL),)
diff --git a/src/pl/plperl/expected/plperl_setup.out b/src/pl/plperl/expected/plperl_setup.out
new file mode 100644
index 0000000..7cb1f25
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_setup.out
@@ -0,0 +1,60 @@
+--
+-- Install the plperl and plperlu extensions
+--
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- suitable permissions have been granted.
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;  -- fail
+ERROR:  permission denied to create extension "plperl"
+HINT:  Must be database owner or member of pg_install_trusted_extension to create this extension.
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+RESET ROLE;
+GRANT pg_install_trusted_extension TO regress_user1;
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+ foo1
+------
+    1
+(1 row)
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+ERROR:  permission denied for language plperl
+SET ROLE regress_user1;
+grant usage on language plperl to regress_user2;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+ foo2
+------
+    2
+(1 row)
+
+SET ROLE regress_user1;
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+ERROR:  cannot drop language plperl because extension plperl requires it
+HINT:  You can drop extension plperl instead.
+DROP EXTENSION plperl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to function foo1()
+drop cascades to function foo2()
+-- Clean up
+RESET ROLE;
+DROP USER regress_user1;
+DROP USER regress_user2;
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plperl/plperl--1.0.sql b/src/pl/plperl/plperl--1.0.sql
index f716ba1..5ff31e7 100644
--- a/src/pl/plperl/plperl--1.0.sql
+++ b/src/pl/plperl/plperl--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plperl/plperl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperl;
+CREATE FUNCTION plperl_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperl_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plperl
+  HANDLER plperl_call_handler
+  INLINE plperl_inline_handler
+  VALIDATOR plperl_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plperl OWNER TO @extowner@;

 COMMENT ON LANGUAGE plperl IS 'PL/Perl procedural language';
diff --git a/src/pl/plperl/plperl.control b/src/pl/plperl/plperl.control
index 6faace1..3a2230a 100644
--- a/src/pl/plperl/plperl.control
+++ b/src/pl/plperl/plperl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plperl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/plperl/plperlu--1.0.sql b/src/pl/plperl/plperlu--1.0.sql
index 7efb4fb..10d7594 100644
--- a/src/pl/plperl/plperlu--1.0.sql
+++ b/src/pl/plperl/plperlu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plperl/plperlu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperlu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperlu;
+CREATE FUNCTION plperlu_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperlu_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plperlu
+  HANDLER plperlu_call_handler
+  INLINE plperlu_inline_handler
+  VALIDATOR plperlu_validator;

 COMMENT ON LANGUAGE plperlu IS 'PL/PerlU untrusted procedural language';
diff --git a/src/pl/plperl/sql/plperl_setup.sql b/src/pl/plperl/sql/plperl_setup.sql
new file mode 100644
index 0000000..9c7b52c
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_setup.sql
@@ -0,0 +1,58 @@
+--
+-- Install the plperl and plperlu extensions
+--
+
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- suitable permissions have been granted.
+
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;  -- fail
+CREATE EXTENSION plperlu;  -- fail
+
+RESET ROLE;
+
+GRANT pg_install_trusted_extension TO regress_user1;
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+
+SET ROLE regress_user1;
+
+grant usage on language plperl to regress_user2;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+
+SET ROLE regress_user1;
+
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+DROP EXTENSION plperl CASCADE;
+
+-- Clean up
+RESET ROLE;
+DROP USER regress_user1;
+DROP USER regress_user2;
+
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plpgsql/src/plpgsql--1.0.sql b/src/pl/plpgsql/src/plpgsql--1.0.sql
index ab6fa84..6e5b990 100644
--- a/src/pl/plpgsql/src/plpgsql--1.0.sql
+++ b/src/pl/plpgsql/src/plpgsql--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plpgsql/src/plpgsql--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpgsql_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpgsql;
+CREATE FUNCTION plpgsql_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpgsql_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plpgsql
+  HANDLER plpgsql_call_handler
+  INLINE plpgsql_inline_handler
+  VALIDATOR plpgsql_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plpgsql OWNER TO @extowner@;

 COMMENT ON LANGUAGE plpgsql IS 'PL/pgSQL procedural language';
diff --git a/src/pl/plpgsql/src/plpgsql.control b/src/pl/plpgsql/src/plpgsql.control
index b320227..42e764b 100644
--- a/src/pl/plpgsql/src/plpgsql.control
+++ b/src/pl/plpgsql/src/plpgsql.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plpgsql'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/plpython/plpython2u--1.0.sql b/src/pl/plpython/plpython2u--1.0.sql
index 661cc66..69f7477 100644
--- a/src/pl/plpython/plpython2u--1.0.sql
+++ b/src/pl/plpython/plpython2u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython2u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython2_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython2u;
+CREATE FUNCTION plpython2_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython2_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython2u
+  HANDLER plpython2_call_handler
+  INLINE plpython2_inline_handler
+  VALIDATOR plpython2_validator;

 COMMENT ON LANGUAGE plpython2u IS 'PL/Python2U untrusted procedural language';
diff --git a/src/pl/plpython/plpython3u--1.0.sql b/src/pl/plpython/plpython3u--1.0.sql
index c0d6ea8..ba2e6ac 100644
--- a/src/pl/plpython/plpython3u--1.0.sql
+++ b/src/pl/plpython/plpython3u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython3u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython3_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython3u;
+CREATE FUNCTION plpython3_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython3_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython3u
+  HANDLER plpython3_call_handler
+  INLINE plpython3_inline_handler
+  VALIDATOR plpython3_validator;

 COMMENT ON LANGUAGE plpython3u IS 'PL/Python3U untrusted procedural language';
diff --git a/src/pl/plpython/plpythonu--1.0.sql b/src/pl/plpython/plpythonu--1.0.sql
index 4a3e64a..4c6f7c3 100644
--- a/src/pl/plpython/plpythonu--1.0.sql
+++ b/src/pl/plpython/plpythonu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpythonu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpythonu;
+CREATE FUNCTION plpython_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpythonu
+  HANDLER plpython_call_handler
+  INLINE plpython_inline_handler
+  VALIDATOR plpython_validator;

 COMMENT ON LANGUAGE plpythonu IS 'PL/PythonU untrusted procedural language';
diff --git a/src/pl/tcl/pltcl--1.0.sql b/src/pl/tcl/pltcl--1.0.sql
index 34a68c8..2ed2b92 100644
--- a/src/pl/tcl/pltcl--1.0.sql
+++ b/src/pl/tcl/pltcl--1.0.sql
@@ -1,11 +1,12 @@
 /* src/pl/tcl/pltcl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltcl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltcl;
+CREATE TRUSTED LANGUAGE pltcl
+  HANDLER pltcl_call_handler;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE pltcl OWNER TO @extowner@;

 COMMENT ON LANGUAGE pltcl IS 'PL/Tcl procedural language';
diff --git a/src/pl/tcl/pltcl.control b/src/pl/tcl/pltcl.control
index b9dc1b8..1568c17 100644
--- a/src/pl/tcl/pltcl.control
+++ b/src/pl/tcl/pltcl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/pltcl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/tcl/pltclu--1.0.sql b/src/pl/tcl/pltclu--1.0.sql
index e05b470..fca869f 100644
--- a/src/pl/tcl/pltclu--1.0.sql
+++ b/src/pl/tcl/pltclu--1.0.sql
@@ -1,11 +1,9 @@
 /* src/pl/tcl/pltclu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltclu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltclu;
+CREATE LANGUAGE pltclu
+  HANDLER pltclu_call_handler;

 COMMENT ON LANGUAGE pltclu IS 'PL/TclU untrusted procedural language';
diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
index 13b28b1..af9d115 100644
--- a/doc/src/sgml/ref/create_language.sgml
+++ b/doc/src/sgml/ref/create_language.sgml
@@ -21,9 +21,9 @@ PostgreSQL documentation

  <refsynopsisdiv>
 <synopsis>
-CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable
class="parameter">inline_handler</replaceable>] [ VALIDATOR <replaceable>valfunction</replaceable> ] 
+CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 </synopsis>
  </refsynopsisdiv>

@@ -37,21 +37,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
    defined in this new language.
   </para>

-  <note>
-   <para>
-    As of <productname>PostgreSQL</productname> 9.1, most procedural
-    languages have been made into <quote>extensions</quote>, and should
-    therefore be installed with <xref linkend="sql-createextension"/>
-    not <command>CREATE LANGUAGE</command>.  Direct use of
-    <command>CREATE LANGUAGE</command> should now be confined to
-    extension installation scripts.  If you have a <quote>bare</quote>
-    language in your database, perhaps as a result of an upgrade,
-    you can convert it to an extension using
-    <literal>CREATE EXTENSION <replaceable>langname</replaceable> FROM
-    unpackaged</literal>.
-   </para>
-  </note>
-
   <para>
    <command>CREATE LANGUAGE</command> effectively associates the
    language name with handler function(s) that are responsible for executing
@@ -60,53 +45,32 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   There are two forms of the <command>CREATE LANGUAGE</command> command.
-   In the first form, the user supplies just the name of the desired
-   language, and the <productname>PostgreSQL</productname> server consults
-   the <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
-   system catalog to determine the correct parameters.  In the second form,
-   the user supplies the language parameters along with the language name.
-   The second form can be used to create a language that is not defined in
-   <structname>pg_pltemplate</structname>, but this approach is considered obsolescent.
-  </para>
-
-  <para>
-   When the server finds an entry in the <structname>pg_pltemplate</structname> catalog
-   for the given language name, it will use the catalog data even if the
-   command includes language parameters.  This behavior simplifies loading of
-   old dump files, which are likely to contain out-of-date information
-   about language support functions.
+   <command>CREATE OR REPLACE LANGUAGE</command> will either create a
+   new language, or replace an existing definition.  If the language
+   already exists, its parameters are updated according to the command,
+   but the language's ownership and permissions settings do not change,
+   and any existing functions written in the language are assumed to still
+   be valid.
   </para>

   <para>
-   Ordinarily, the user must have the
+   One must have the
    <productname>PostgreSQL</productname> superuser privilege to
-   register a new language.  However, the owner of a database can register
-   a new language within that database if the language is listed in
-   the <structname>pg_pltemplate</structname> catalog and is marked
-   as allowed to be created by database owners (<structfield>tmpldbacreate</structfield>
-   is true).  The default is that trusted languages can be created
-   by database owners, but this can be adjusted by superusers by modifying
-   the contents of <structname>pg_pltemplate</structname>.
-   The creator of a language becomes its owner and can later
-   drop it, rename it, or assign it to a new owner.
+   register a new language or change an existing language's parameters.
+   However, once the language is created it is valid to assign ownership of
+   it to a non-superuser, who may then drop it, change its permissions,
+   rename it, or assign it to a new owner.  (Do not, however, assign
+   ownership of the underlying C functions to a non-superuser; that would
+   create a privilege escalation path for that user.)
   </para>

   <para>
-   <command>CREATE OR REPLACE LANGUAGE</command> will either create a
-   new language, or replace an existing definition.  If the language
-   already exists, its parameters are updated according to the values
-   specified or taken from <structname>pg_pltemplate</structname>,
-   but the language's ownership and permissions settings do not change,
-   and any existing functions written in the language are assumed to still
-   be valid.  In addition to the normal privilege requirements for creating
-   a language, the user must be superuser or owner of the existing language.
-   The <literal>REPLACE</literal> case is mainly meant to be used to
-   ensure that the language exists.  If the language has a
-   <structname>pg_pltemplate</structname> entry then <literal>REPLACE</literal>
-   will not actually change anything about an existing definition, except in
-   the unusual case where the <structname>pg_pltemplate</structname> entry
-   has been modified since the language was created.
+   The form of <command>CREATE LANGUAGE</command> that does not supply
+   any handler function is obsolete.  For backwards compatibility with
+   old dump files, it is interpreted as <command>CREATE EXTENSION</command>.
+   That will work if the language has been packaged into an extension of
+   the same name, which is the conventional way to set up procedural
+   languages.
   </para>
  </refsect1>

@@ -218,12 +182,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
      </listitem>
     </varlistentry>
    </variablelist>
-
-  <para>
-   The <literal>TRUSTED</literal> option and the support function name(s) are
-   ignored if the server has an entry for the specified language
-   name in <structname>pg_pltemplate</structname>.
-  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-notes">
@@ -255,18 +213,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   The call handler function, the inline handler function (if any),
-   and the validator function (if any)
-   must already exist if the server does not have an entry for the language
-   in <structname>pg_pltemplate</structname>.  But when there is an entry,
-   the functions need not already exist;
-   they will be automatically defined if not present in the database.
-   (This might result in <command>CREATE LANGUAGE</command> failing, if the
-   shared library that implements the language is not available in
-   the installation.)
-  </para>
-
-  <para>
    In <productname>PostgreSQL</productname> versions before 7.3, it was
    necessary to declare handler functions as returning the placeholder
    type <type>opaque</type>, rather than <type>language_handler</type>.
@@ -281,23 +227,20 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   <title>Examples</title>

   <para>
-   The preferred way of creating any of the standard procedural languages
-   is just:
-<programlisting>
-CREATE LANGUAGE plperl;
-</programlisting>
-  </para>
-
-  <para>
-   For a language not known in the <structname>pg_pltemplate</structname> catalog, a
-   sequence such as this is needed:
+   A minimal sequence for creating a new procedural language is:
 <programlisting>
 CREATE FUNCTION plsample_call_handler() RETURNS language_handler
     AS '$libdir/plsample'
     LANGUAGE C;
 CREATE LANGUAGE plsample
     HANDLER plsample_call_handler;
-</programlisting></para>
+</programlisting>
+   Typically that would be written in an extension's creation script,
+   and users would do this to install the extension:
+<programlisting>
+CREATE EXTENSION plsample;
+</programlisting>
+  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-compat">
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 339ba5b..a63a39f 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2277,6 +2277,64 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 }

 /*
+ * Test whether the given extension exists (not whether it's installed)
+ *
+ * This checks for the existence of a matching control file in the extension
+ * directory.  That's not a bulletproof check, since the file might be
+ * invalid, but this is only used for hints so it doesn't have to be 100%
+ * right.
+ */
+bool
+extension_file_exists(const char *extensionName)
+{
+    bool        result = false;
+    char       *location;
+    DIR           *dir;
+    struct dirent *de;
+
+    location = get_extension_control_directory();
+    dir = AllocateDir(location);
+
+    /*
+     * If the control directory doesn't exist, we want to silently return
+     * false.  Any other error will be reported by ReadDir.
+     */
+    if (dir == NULL && errno == ENOENT)
+    {
+        /* do nothing */
+    }
+    else
+    {
+        while ((de = ReadDir(dir, location)) != NULL)
+        {
+            char       *extname;
+
+            if (!is_extension_control_filename(de->d_name))
+                continue;
+
+            /* extract extension name from 'name.control' filename */
+            extname = pstrdup(de->d_name);
+            *strrchr(extname, '.') = '\0';
+
+            /* ignore it if it's an auxiliary control file */
+            if (strstr(extname, "--"))
+                continue;
+
+            /* done if it matches request */
+            if (strcmp(extname, extensionName) == 0)
+            {
+                result = true;
+                break;
+            }
+        }
+
+        FreeDir(dir);
+    }
+
+    return result;
+}
+
+/*
  * Convert a list of extension names to a name[] Datum
  */
 static Datum
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index c31c57e..0f40c9e 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/extension.h"
 #include "commands/proclang.h"
 #include "executor/execdesc.h"
 #include "executor/executor.h"
@@ -991,7 +992,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_OBJECT),
                  errmsg("language \"%s\" does not exist", language),
-                 (PLTemplateExists(language) ?
+                 (extension_file_exists(language) ?
                   errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

     languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
@@ -2225,7 +2226,7 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_OBJECT),
                  errmsg("language \"%s\" does not exist", language),
-                 (PLTemplateExists(language) ?
+                 (extension_file_exists(language) ?
                   errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

     languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index cdff43d..9d72edb 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -13,329 +13,110 @@
  */
 #include "postgres.h"

-#include "access/genam.h"
-#include "access/htup_details.h"
 #include "access/table.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
-#include "catalog/pg_authid.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
-#include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/proclang.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
-#include "parser/parser.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
-#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"


-typedef struct
-{
-    bool        tmpltrusted;    /* trusted? */
-    bool        tmpldbacreate;    /* db owner allowed to create? */
-    char       *tmplhandler;    /* name of handler function */
-    char       *tmplinline;        /* name of anonymous-block handler, or NULL */
-    char       *tmplvalidator;    /* name of validator function, or NULL */
-    char       *tmpllibrary;    /* path of shared library */
-} PLTemplate;
-
-static ObjectAddress create_proc_lang(const char *languageName, bool replace,
-                                      Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                                      Oid valOid, bool trusted);
-static PLTemplate *find_language_template(const char *languageName);
-
 /*
  * CREATE LANGUAGE
  */
 ObjectAddress
 CreateProceduralLanguage(CreatePLangStmt *stmt)
 {
-    PLTemplate *pltemplate;
-    ObjectAddress tmpAddr;
+    const char *languageName = stmt->plname;
+    Oid            languageOwner = GetUserId();
     Oid            handlerOid,
                 inlineOid,
                 valOid;
     Oid            funcrettype;
     Oid            funcargtypes[1];
+    Relation    rel;
+    TupleDesc    tupDesc;
+    Datum        values[Natts_pg_language];
+    bool        nulls[Natts_pg_language];
+    bool        replaces[Natts_pg_language];
+    NameData    langname;
+    HeapTuple    oldtup;
+    HeapTuple    tup;
+    Oid            langoid;
+    bool        is_update;
+    ObjectAddress myself,
+                referenced;

     /*
-     * If we have template information for the language, ignore the supplied
-     * parameters (if any) and use the template information.
+     * Check permission
      */
-    if ((pltemplate = find_language_template(stmt->plname)) != NULL)
-    {
-        List       *funcname;
-
-        /*
-         * Give a notice if we are ignoring supplied parameters.
-         */
-        if (stmt->plhandler)
-            ereport(NOTICE,
-                    (errmsg("using pg_pltemplate information instead of CREATE LANGUAGE parameters")));
-
-        /*
-         * Check permission
-         */
-        if (!superuser())
-        {
-            if (!pltemplate->tmpldbacreate)
-                ereport(ERROR,
-                        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                         errmsg("must be superuser to create procedural language \"%s\"",
-                                stmt->plname)));
-            if (!pg_database_ownercheck(MyDatabaseId, GetUserId()))
-                aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
-                               get_database_name(MyDatabaseId));
-        }
-
-        /*
-         * Find or create the handler function, which we force to be in the
-         * pg_catalog schema.  If already present, it must have the correct
-         * return type.
-         */
-        funcname = SystemFuncName(pltemplate->tmplhandler);
-        handlerOid = LookupFuncName(funcname, 0, NULL, true);
-        if (OidIsValid(handlerOid))
-        {
-            funcrettype = get_func_rettype(handlerOid);
-            if (funcrettype != LANGUAGE_HANDLEROID)
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(funcname), "language_handler")));
-        }
-        else
-        {
-            tmpAddr = ProcedureCreate(pltemplate->tmplhandler,
-                                      PG_CATALOG_NAMESPACE,
-                                      false,    /* replace */
-                                      false,    /* returnsSet */
-                                      LANGUAGE_HANDLEROID,
-                                      BOOTSTRAP_SUPERUSERID,
-                                      ClanguageId,
-                                      F_FMGR_C_VALIDATOR,
-                                      pltemplate->tmplhandler,
-                                      pltemplate->tmpllibrary,
-                                      PROKIND_FUNCTION,
-                                      false,    /* security_definer */
-                                      false,    /* isLeakProof */
-                                      false,    /* isStrict */
-                                      PROVOLATILE_VOLATILE,
-                                      PROPARALLEL_UNSAFE,
-                                      buildoidvector(funcargtypes, 0),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      NIL,
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      InvalidOid,
-                                      1,
-                                      0);
-            handlerOid = tmpAddr.objectId;
-        }
-
-        /*
-         * Likewise for the anonymous block handler, if required; but we don't
-         * care about its return type.
-         */
-        if (pltemplate->tmplinline)
-        {
-            funcname = SystemFuncName(pltemplate->tmplinline);
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(inlineOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplinline,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplinline,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                inlineOid = tmpAddr.objectId;
-            }
-        }
-        else
-            inlineOid = InvalidOid;
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 errmsg("must be superuser to create custom procedural language")));

+    /*
+     * Lookup the PL handler function and check that it is of the expected
+     * return type
+     */
+    Assert(stmt->plhandler);
+    handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false);
+    funcrettype = get_func_rettype(handlerOid);
+    if (funcrettype != LANGUAGE_HANDLEROID)
+    {
         /*
-         * Likewise for the validator, if required; but we don't care about
-         * its return type.
+         * We allow OPAQUE just so we can load old dump files.  When we see a
+         * handler function declared OPAQUE, change it to LANGUAGE_HANDLER.
+         * (This is probably obsolete and removable?)
          */
-        if (pltemplate->tmplvalidator)
+        if (funcrettype == OPAQUEOID)
         {
-            funcname = SystemFuncName(pltemplate->tmplvalidator);
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(valOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplvalidator,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplvalidator,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                valOid = tmpAddr.objectId;
-            }
+            ereport(WARNING,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("changing return type of function %s from %s to %s",
+                            NameListToString(stmt->plhandler),
+                            "opaque", "language_handler")));
+            SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
         }
         else
-            valOid = InvalidOid;
+            ereport(ERROR,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("function %s must return type %s",
+                            NameListToString(stmt->plhandler), "language_handler")));
+    }

-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, pltemplate->tmpltrusted);
+    /* validate the inline function */
+    if (stmt->plinline)
+    {
+        funcargtypes[0] = INTERNALOID;
+        inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
     else
-    {
-        /*
-         * No template, so use the provided information.  If there's no
-         * handler clause, the user is trying to rely on a template that we
-         * don't have, so complain accordingly.
-         */
-        if (!stmt->plhandler)
-            ereport(ERROR,
-                    (errcode(ERRCODE_UNDEFINED_OBJECT),
-                     errmsg("unsupported language \"%s\"",
-                            stmt->plname),
-                     errhint("The supported languages are listed in the pg_pltemplate system catalog.")));
+        inlineOid = InvalidOid;

-        /*
-         * Check permission
-         */
-        if (!superuser())
-            ereport(ERROR,
-                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                     errmsg("must be superuser to create custom procedural language")));
-
-        /*
-         * Lookup the PL handler function and check that it is of the expected
-         * return type
-         */
-        handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false);
-        funcrettype = get_func_rettype(handlerOid);
-        if (funcrettype != LANGUAGE_HANDLEROID)
-        {
-            /*
-             * We allow OPAQUE just so we can load old dump files.  When we
-             * see a handler function declared OPAQUE, change it to
-             * LANGUAGE_HANDLER.  (This is probably obsolete and removable?)
-             */
-            if (funcrettype == OPAQUEOID)
-            {
-                ereport(WARNING,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("changing return type of function %s from %s to %s",
-                                NameListToString(stmt->plhandler),
-                                "opaque", "language_handler")));
-                SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
-            }
-            else
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(stmt->plhandler), "language_handler")));
-        }
-
-        /* validate the inline function */
-        if (stmt->plinline)
-        {
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            inlineOid = InvalidOid;
-
-        /* validate the validator function */
-        if (stmt->plvalidator)
-        {
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            valOid = InvalidOid;
-
-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, stmt->pltrusted);
+    /* validate the validator function */
+    if (stmt->plvalidator)
+    {
+        funcargtypes[0] = OIDOID;
+        valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
-}
-
-/*
- * Guts of language creation.
- */
-static ObjectAddress
-create_proc_lang(const char *languageName, bool replace,
-                 Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                 Oid valOid, bool trusted)
-{
-    Relation    rel;
-    TupleDesc    tupDesc;
-    Datum        values[Natts_pg_language];
-    bool        nulls[Natts_pg_language];
-    bool        replaces[Natts_pg_language];
-    NameData    langname;
-    HeapTuple    oldtup;
-    HeapTuple    tup;
-    Oid            langoid;
-    bool        is_update;
-    ObjectAddress myself,
-                referenced;
+    else
+        valOid = InvalidOid;

+    /* ok to create it */
     rel = table_open(LanguageRelationId, RowExclusiveLock);
     tupDesc = RelationGetDescr(rel);

@@ -348,7 +129,7 @@ create_proc_lang(const char *languageName, bool replace,
     values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname);
     values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner);
     values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true);
-    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(trusted);
+    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(stmt->pltrusted);
     values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
     values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
     values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
@@ -362,13 +143,17 @@ create_proc_lang(const char *languageName, bool replace,
         Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup);

         /* There is one; okay to replace it? */
-        if (!replace)
+        if (!stmt->replace)
             ereport(ERROR,
                     (errcode(ERRCODE_DUPLICATE_OBJECT),
                      errmsg("language \"%s\" already exists", languageName)));
+
+        /* This is currently pointless, since we already checked superuser */
+#ifdef NOT_USED
         if (!pg_language_ownercheck(oldform->oid, languageOwner))
             aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE,
                            languageName);
+#endif

         /*
          * Do not change existing oid, ownership or permissions.  Note
@@ -451,83 +236,6 @@ create_proc_lang(const char *languageName, bool replace,
 }

 /*
- * Look to see if we have template information for the given language name.
- */
-static PLTemplate *
-find_language_template(const char *languageName)
-{
-    PLTemplate *result;
-    Relation    rel;
-    SysScanDesc scan;
-    ScanKeyData key;
-    HeapTuple    tup;
-
-    rel = table_open(PLTemplateRelationId, AccessShareLock);
-
-    ScanKeyInit(&key,
-                Anum_pg_pltemplate_tmplname,
-                BTEqualStrategyNumber, F_NAMEEQ,
-                CStringGetDatum(languageName));
-    scan = systable_beginscan(rel, PLTemplateNameIndexId, true,
-                              NULL, 1, &key);
-
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_pltemplate tmpl = (Form_pg_pltemplate) GETSTRUCT(tup);
-        Datum        datum;
-        bool        isnull;
-
-        result = (PLTemplate *) palloc0(sizeof(PLTemplate));
-        result->tmpltrusted = tmpl->tmpltrusted;
-        result->tmpldbacreate = tmpl->tmpldbacreate;
-
-        /* Remaining fields are variable-width so we need heap_getattr */
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplhandler,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplhandler = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplinline,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplinline = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplvalidator,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplvalidator = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmpllibrary = TextDatumGetCString(datum);
-
-        /* Ignore template if handler or library info is missing */
-        if (!result->tmplhandler || !result->tmpllibrary)
-            result = NULL;
-    }
-    else
-        result = NULL;
-
-    systable_endscan(scan);
-
-    table_close(rel, AccessShareLock);
-
-    return result;
-}
-
-
-/*
- * This just returns true if we have a valid template for a given language
- */
-bool
-PLTemplateExists(const char *languageName)
-{
-    return (find_language_template(languageName) != NULL);
-}
-
-/*
  * Guts of language dropping.
  */
 void
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ad5be90..1bd5ed4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4304,14 +4304,17 @@ NumericOnly_list:    NumericOnly                        { $$ = list_make1($1); }
 CreatePLangStmt:
             CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
             {
-                CreatePLangStmt *n = makeNode(CreatePLangStmt);
-                n->replace = $2;
-                n->plname = $6;
-                /* parameters are all to be supplied by system */
-                n->plhandler = NIL;
-                n->plinline = NIL;
-                n->plvalidator = NIL;
-                n->pltrusted = false;
+                /*
+                 * We now interpret parameterless CREATE LANGUAGE as
+                 * CREATE EXTENSION.  "OR REPLACE" is silently translated
+                 * to "IF NOT EXISTS", which isn't quite the same, but
+                 * seems more useful than throwing an error.  We just
+                 * ignore TRUSTED, as the previous code would have too.
+                 */
+                CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
+                n->if_not_exists = $2;
+                n->extname = $6;
+                n->options = NIL;
                 $$ = (Node *)n;
             }
             | CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 6f10707..7923cdc 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -47,6 +47,7 @@ extern ObjectAddress ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *

 extern Oid    get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
+extern bool extension_file_exists(const char *extensionName);

 extern ObjectAddress AlterExtensionNamespace(const char *extensionName, const char *newschema,
                                              Oid *oldschema);
diff --git a/src/include/commands/proclang.h b/src/include/commands/proclang.h
index 9a4bc75..c70f8ec 100644
--- a/src/include/commands/proclang.h
+++ b/src/include/commands/proclang.h
@@ -1,11 +1,12 @@
-/*
- * src/include/commands/proclang.h
- *
- *-------------------------------------------------------------------------
+/*-------------------------------------------------------------------------
  *
  * proclang.h
  *      prototypes for proclang.c.
  *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/proclang.h
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +18,7 @@

 extern ObjectAddress CreateProceduralLanguage(CreatePLangStmt *stmt);
 extern void DropProceduralLanguageById(Oid langOid);
-extern bool PLTemplateExists(const char *languageName);
+
 extern Oid    get_language_oid(const char *langname, bool missing_ok);

 #endif                            /* PROCLANG_H */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index a573dfb..03ad677 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,11 +226,6 @@
      </row>

      <row>
-      <entry><link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link></entry>
-      <entry>template data for procedural languages</entry>
-     </row>
-
-     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -4911,113 +4906,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
  </sect1>


- <sect1 id="catalog-pg-pltemplate">
-  <title><structname>pg_pltemplate</structname></title>
-
-  <indexterm zone="catalog-pg-pltemplate">
-   <primary>pg_pltemplate</primary>
-  </indexterm>
-
-  <para>
-   The catalog <structname>pg_pltemplate</structname> stores
-   <quote>template</quote> information for procedural languages.
-   A template for a language allows the language to be created in a
-   particular database by a simple <command>CREATE LANGUAGE</command> command,
-   with no need to specify implementation details.
-  </para>
-
-  <para>
-   Unlike most system catalogs, <structname>pg_pltemplate</structname>
-   is shared across all databases of a cluster: there is only one
-   copy of <structname>pg_pltemplate</structname> per cluster, not
-   one per database.  This allows the information to be accessible in
-   each database as it is needed.
-  </para>
-
-  <table>
-   <title><structname>pg_pltemplate</structname> Columns</title>
-
-   <tgroup cols="3">
-    <thead>
-     <row>
-      <entry>Name</entry>
-      <entry>Type</entry>
-      <entry>Description</entry>
-     </row>
-    </thead>
-
-    <tbody>
-     <row>
-      <entry><structfield>tmplname</structfield></entry>
-      <entry><type>name</type></entry>
-      <entry>Name of the language this template is for</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpltrusted</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language is considered trusted</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpldbacreate</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language may be created by a database owner</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplhandler</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of call handler function</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplinline</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of anonymous-block handler function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplvalidator</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of validator function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpllibrary</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Path of shared library that implements language</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplacl</structfield></entry>
-      <entry><type>aclitem[]</type></entry>
-      <entry>Access privileges for template (not actually used)</entry>
-     </row>
-
-    </tbody>
-   </tgroup>
-  </table>
-
-  <para>
-   There are not currently any commands that manipulate procedural language
-   templates; to change the built-in information, a superuser must modify
-   the table using ordinary <command>INSERT</command>, <command>DELETE</command>,
-   or <command>UPDATE</command> commands.
-  </para>
-
-  <note>
-   <para>
-    It is likely that <structname>pg_pltemplate</structname> will be removed in some
-    future release of <productname>PostgreSQL</productname>, in favor of
-    keeping this knowledge about procedural languages in their respective
-    extension installation scripts.
-   </para>
-  </note>
-
- </sect1>
-
-
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>

diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index c421092..19ecb56 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -153,7 +153,7 @@
      <para>
       Daredevils, who want to build a Python-3-only operating system
       environment, can change the contents of
-      <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
+      <literal>plpythonu</literal>'s extension control and script files
       to make <literal>plpythonu</literal> be equivalent
       to <literal>plpython3u</literal>, keeping in mind that this
       would make their installation incompatible with most of the rest
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 61db650..f6ab3ea 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -60,7 +60,7 @@ CATALOG_HEADERS := \
     pg_statistic_ext.h pg_statistic_ext_data.h \
     pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
     pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
-    pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
+    pg_database.h pg_db_role_setting.h pg_tablespace.h \
     pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
     pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
     pg_ts_parser.h pg_ts_template.h pg_extension.h \
@@ -86,7 +86,7 @@ POSTGRES_BKI_DATA = $(addprefix $(top_srcdir)/src/include/catalog/,\
     pg_cast.dat pg_class.dat pg_collation.dat pg_conversion.dat \
     pg_database.dat pg_language.dat \
     pg_namespace.dat pg_opclass.dat pg_operator.dat pg_opfamily.dat \
-    pg_pltemplate.dat pg_proc.dat pg_range.dat pg_tablespace.dat \
+    pg_proc.dat pg_range.dat pg_tablespace.dat \
     pg_ts_config.dat pg_ts_config_map.dat pg_ts_dict.dat pg_ts_parser.dat \
     pg_ts_template.dat pg_type.dat \
     )
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 16cb6d8..7d6acae 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -33,7 +33,6 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
 #include "catalog/pg_shdescription.h"
@@ -242,7 +241,6 @@ IsSharedRelation(Oid relationId)
     if (relationId == AuthIdRelationId ||
         relationId == AuthMemRelationId ||
         relationId == DatabaseRelationId ||
-        relationId == PLTemplateRelationId ||
         relationId == SharedDescriptionRelationId ||
         relationId == SharedDependRelationId ||
         relationId == SharedSecLabelRelationId ||
@@ -258,7 +256,6 @@ IsSharedRelation(Oid relationId)
         relationId == AuthMemMemRoleIndexId ||
         relationId == DatabaseNameIndexId ||
         relationId == DatabaseOidIndexId ||
-        relationId == PLTemplateNameIndexId ||
         relationId == SharedDescriptionObjIndexId ||
         relationId == SharedDependDependerIndexId ||
         relationId == SharedDependReferenceIndexId ||
@@ -278,8 +275,6 @@ IsSharedRelation(Oid relationId)
         relationId == PgDatabaseToastIndex ||
         relationId == PgDbRoleSettingToastTable ||
         relationId == PgDbRoleSettingToastIndex ||
-        relationId == PgPlTemplateToastTable ||
-        relationId == PgPlTemplateToastIndex ||
         relationId == PgReplicationOriginToastTable ||
         relationId == PgReplicationOriginToastIndex ||
         relationId == PgShdescriptionToastTable ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 799b698..ec3e2c6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11396,9 +11396,9 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
     }

     /*
-     * If the functions are dumpable then emit a traditional CREATE LANGUAGE
-     * with parameters.  Otherwise, we'll write a parameterless command, which
-     * will rely on data from pg_pltemplate.
+     * If the functions are dumpable then emit a complete CREATE LANGUAGE with
+     * parameters.  Otherwise, we'll write a parameterless command, which will
+     * be interpreted as CREATE EXTENSION.
      */
     useParams = (funcInfo != NULL &&
                  (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
@@ -11431,11 +11431,11 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
         /*
          * If not dumping parameters, then use CREATE OR REPLACE so that the
          * command will not fail if the language is preinstalled in the target
-         * database.  We restrict the use of REPLACE to this case so as to
-         * eliminate the risk of replacing a language with incompatible
-         * parameter settings: this command will only succeed at all if there
-         * is a pg_pltemplate entry, and if there is one, the existing entry
-         * must match it too.
+         * database.
+         *
+         * Modern servers will interpret this as CREATE EXTENSION IF NOT
+         * EXISTS; perhaps we should emit that instead?  But it might just add
+         * confusion.
          */
         appendPQExpBuffer(defqry, "CREATE OR REPLACE PROCEDURAL LANGUAGE %s",
                           qlanname);
diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c
index 28638e9..d163cb2 100644
--- a/src/bin/pg_upgrade/function.c
+++ b/src/bin/pg_upgrade/function.c
@@ -214,13 +214,9 @@ check_loadable_libraries(void)
              * plpython2u language was created with library name plpython2.so
              * as a symbolic link to plpython.so.  In Postgres 9.1, only the
              * plpython2.so library was created, and both plpythonu and
-             * plpython2u pointing to it.  For this reason, any reference to
+             * plpython2u point to it.  For this reason, any reference to
              * library name "plpython" in an old PG <= 9.1 cluster must look
              * for "plpython2" in the new cluster.
-             *
-             * For this case, we could check pg_pltemplate, but that only
-             * works for languages, and does not help with function shared
-             * objects, so we just do a general fix.
              */
             if (GET_MAJOR_VERSION(old_cluster.major_version) < 901 &&
                 strcmp(lib, "$libdir/plpython") == 0)
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 13e07fc..8be3038 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -206,9 +206,6 @@ DECLARE_UNIQUE_INDEX(pg_opfamily_am_name_nsp_index, 2754, on pg_opfamily using b
 DECLARE_UNIQUE_INDEX(pg_opfamily_oid_index, 2755, on pg_opfamily using btree(oid oid_ops));
 #define OpfamilyOidIndexId    2755

-DECLARE_UNIQUE_INDEX(pg_pltemplate_name_index, 1137, on pg_pltemplate using btree(tmplname name_ops));
-#define PLTemplateNameIndexId  1137
-
 DECLARE_UNIQUE_INDEX(pg_proc_oid_index, 2690, on pg_proc using btree(oid oid_ops));
 #define ProcedureOidIndexId  2690
 DECLARE_UNIQUE_INDEX(pg_proc_proname_args_nsp_index, 2691, on pg_proc using btree(proname name_ops, proargtypes
oidvector_ops,pronamespace oid_ops)); 
diff --git a/src/include/catalog/pg_pltemplate.dat b/src/include/catalog/pg_pltemplate.dat
deleted file mode 100644
index 1c96b30..0000000
--- a/src/include/catalog/pg_pltemplate.dat
+++ /dev/null
@@ -1,51 +0,0 @@
-#----------------------------------------------------------------------
-#
-# pg_pltemplate.dat
-#    Initial contents of the pg_pltemplate system catalog.
-#
-# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
-# Portions Copyright (c) 1994, Regents of the University of California
-#
-# src/include/catalog/pg_pltemplate.dat
-#
-#----------------------------------------------------------------------
-
-[
-
-{ tmplname => 'plpgsql', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plpgsql_call_handler', tmplinline => 'plpgsql_inline_handler',
-  tmplvalidator => 'plpgsql_validator', tmpllibrary => '$libdir/plpgsql',
-  tmplacl => '_null_' },
-{ tmplname => 'pltcl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'pltcl_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'pltclu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'pltclu_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plperl_call_handler', tmplinline => 'plperl_inline_handler',
-  tmplvalidator => 'plperl_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperlu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plperlu_call_handler', tmplinline => 'plperlu_inline_handler',
-  tmplvalidator => 'plperlu_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plpythonu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython_call_handler',
-  tmplinline => 'plpython_inline_handler',
-  tmplvalidator => 'plpython_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython2u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython2_call_handler',
-  tmplinline => 'plpython2_inline_handler',
-  tmplvalidator => 'plpython2_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython3u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython3_call_handler',
-  tmplinline => 'plpython3_inline_handler',
-  tmplvalidator => 'plpython3_validator', tmpllibrary => '$libdir/plpython3',
-  tmplacl => '_null_' },
-
-]
diff --git a/src/include/catalog/pg_pltemplate.h b/src/include/catalog/pg_pltemplate.h
deleted file mode 100644
index b930730..0000000
--- a/src/include/catalog/pg_pltemplate.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * pg_pltemplate.h
- *      definition of the "PL template" system catalog (pg_pltemplate)
- *
- *
- * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/catalog/pg_pltemplate.h
- *
- * NOTES
- *      The Catalog.pm module reads this file and derives schema
- *      information.
- *
- *-------------------------------------------------------------------------
- */
-#ifndef PG_PLTEMPLATE_H
-#define PG_PLTEMPLATE_H
-
-#include "catalog/genbki.h"
-#include "catalog/pg_pltemplate_d.h"
-
-/* ----------------
- *        pg_pltemplate definition.  cpp turns this into
- *        typedef struct FormData_pg_pltemplate
- * ----------------
- */
-CATALOG(pg_pltemplate,1136,PLTemplateRelationId) BKI_SHARED_RELATION
-{
-    NameData    tmplname;        /* name of PL */
-    bool        tmpltrusted;    /* PL is trusted? */
-    bool        tmpldbacreate;    /* PL is installable by db owner? */
-
-#ifdef CATALOG_VARLEN            /* variable-length fields start here */
-    text        tmplhandler BKI_FORCE_NOT_NULL; /* name of call handler
-                                                 * function */
-    text        tmplinline;        /* name of anonymous-block handler, or NULL */
-    text        tmplvalidator;    /* name of validator function, or NULL */
-    text        tmpllibrary BKI_FORCE_NOT_NULL; /* path of shared library */
-    aclitem        tmplacl[1];        /* access privileges for template */
-#endif
-} FormData_pg_pltemplate;
-
-/* ----------------
- *        Form_pg_pltemplate corresponds to a pointer to a row with
- *        the format of pg_pltemplate relation.
- * ----------------
- */
-typedef FormData_pg_pltemplate *Form_pg_pltemplate;
-
-#endif                            /* PG_PLTEMPLATE_H */
diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h
index 3281dbd..51491c4 100644
--- a/src/include/catalog/toasting.h
+++ b/src/include/catalog/toasting.h
@@ -86,9 +86,6 @@ DECLARE_TOAST(pg_database, 4177, 4178);
 DECLARE_TOAST(pg_db_role_setting, 2966, 2967);
 #define PgDbRoleSettingToastTable 2966
 #define PgDbRoleSettingToastIndex 2967
-DECLARE_TOAST(pg_pltemplate, 4179, 4180);
-#define PgPlTemplateToastTable 4179
-#define PgPlTemplateToastIndex 4180
 DECLARE_TOAST(pg_replication_origin, 4181, 4182);
 #define PgReplicationOriginToastTable 4181
 #define PgReplicationOriginToastIndex 4182
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index faaec55..882d69e 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -29,7 +29,7 @@
  */

 #if PY_MAJOR_VERSION >= 3
-/* Use separate names to avoid clash in pg_pltemplate */
+/* Use separate names to reduce confusion */
 #define plpython_validator plpython3_validator
 #define plpython_call_handler plpython3_call_handler
 #define plpython_inline_handler plpython3_inline_handler
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 070de78..f0cd40b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -134,7 +134,6 @@ pg_opclass|t
 pg_operator|t
 pg_opfamily|t
 pg_partitioned_table|t
-pg_pltemplate|t
 pg_policy|t
 pg_proc|t
 pg_publication|t

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Thu, Nov 7, 2019 at 2:13 PM Stephen Frost <sfrost@snowman.net> wrote:
> I do not agree that we should just shift to using default roles instead
> of adding new options to GRANT because of an entirely internal
> implementation detail that we could fix (and should, as I've said for
> probably 10 years now...).

+1.

I'm not sure that Tom's latest design idea is a bad one, but I
strongly suspect that wrapping ourselves around the axle to work
around our unwillingness to widen a 16-bit quantity to 32 bits (or a
32 bit quantity to 64 bits) is a bad idea. Perhaps there are also
design ideas that we should consider, like separating "basic"
privileges and "extended" privileges or coming up with some altogether
new and better representation. But limiting ourselves to 4 more
privileges ever cannot be the right solution.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> On Thu, Nov 7, 2019 at 2:13 PM Stephen Frost <sfrost@snowman.net> wrote:
>> I do not agree that we should just shift to using default roles instead
>> of adding new options to GRANT because of an entirely internal
>> implementation detail that we could fix (and should, as I've said for
>> probably 10 years now...).

> +1.

> I'm not sure that Tom's latest design idea is a bad one, but I
> strongly suspect that wrapping ourselves around the axle to work
> around our unwillingness to widen a 16-bit quantity to 32 bits (or a
> 32 bit quantity to 64 bits) is a bad idea. Perhaps there are also
> design ideas that we should consider, like separating "basic"
> privileges and "extended" privileges or coming up with some altogether
> new and better representation. But limiting ourselves to 4 more
> privileges ever cannot be the right solution.

So, is that actually an objection to the current proposal, or just
an unrelated rant?

If we think that a privilege bit on databases can actually add something
useful to this design, the fact that it moves us one bit closer to needing
to widen AclMode doesn't seem like a serious objection.  But I don't
actually see what such a bit will buy for this purpose.  A privilege bit
on a database is presumably something that can be granted or revoked by
the database owner, and I do not see that we want any such behavior for
extension installation privileges.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
> > On Thu, Nov 7, 2019 at 2:13 PM Stephen Frost <sfrost@snowman.net> wrote:
> >> I do not agree that we should just shift to using default roles instead
> >> of adding new options to GRANT because of an entirely internal
> >> implementation detail that we could fix (and should, as I've said for
> >> probably 10 years now...).
>
> > +1.
>
> > I'm not sure that Tom's latest design idea is a bad one, but I
> > strongly suspect that wrapping ourselves around the axle to work
> > around our unwillingness to widen a 16-bit quantity to 32 bits (or a
> > 32 bit quantity to 64 bits) is a bad idea. Perhaps there are also
> > design ideas that we should consider, like separating "basic"
> > privileges and "extended" privileges or coming up with some altogether
> > new and better representation. But limiting ourselves to 4 more
> > privileges ever cannot be the right solution.
>
> So, is that actually an objection to the current proposal, or just
> an unrelated rant?

It strikes me as related since using a bit was one of the objections to
using the GRANT-a-privilege approach.

> If we think that a privilege bit on databases can actually add something
> useful to this design, the fact that it moves us one bit closer to needing
> to widen AclMode doesn't seem like a serious objection.  But I don't
> actually see what such a bit will buy for this purpose.  A privilege bit
> on a database is presumably something that can be granted or revoked by
> the database owner, and I do not see that we want any such behavior for
> extension installation privileges.

Given that extensions are database-level objects, I ask: why not?
Database owners are already able to create schema, and therefore to
create any object inside an extension that doesn't require a superuser
to create, why not let them also create the framework for those objects
to exist in, in the form of an extension?

When it comes to *trusted* extensions, I would view those in basically
the exact same way we view *trusted* languages- that is, if they're
trusted, then they can't be used to bypass the privilege system that
exists in PG, nor can they be used to operate directly on the filesystem
or open sockets, etc, at least- not without further checks.  For
example, I would think postgres_fdw could be a 'trusted' extension,
since it only allows superusers to create FDWs, and you can't create a
server unless you have rights on the FDW.

When it comes to *untrusted* extensions, we could limit those to being
only installable by superusers, in the same way that functions in
untrusted languages are only able to be created by superusers (except,
perhaps as part of a trusted extension, assuming we can work through
this).

Now, I'm no fan of growing the set of things that only a superuser can
do, but I don't see that as being what we're doing here because we're
(hopefully) going to at least make it so that non-superusers can do some
things (create trusted extensions) that used to only be possible for
superusers to do, even if it still requires being a superuser to create
untrusted extensions.  If someone comes up with a really strong use-case
then for allowing non-superusers to create untrusted extensions, then we
could consider how to enable that and maybe a default role makes sense
for that specific case, but I don't think anyone's really made that
case and I certainly don't think we want the privilege to create trusted
extensions and the privilege to create untrusted ones to be the same-
it's clear made that users will want to grant out those abilities
independently.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Mon, Jan 6, 2020 at 1:27 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> So, is that actually an objection to the current proposal, or just
> an unrelated rant?

Well, you brought up the topic of remaining bits in the context of the
proposal, so I guess it's related. And I said pretty clearly that it
wasn't necessarily an objection.

But regarding your proposal:

> After sleeping on it, I'm liking that idea; it's simple, and it
> preserves the existing behavior that DB owners can install trusted PLs
> without any extra permissions.  Now, if we follow this up by marking
> most of contrib as trusted, we'd be expanding that existing privilege.
> But I think that's all right: I don't recall anybody ever complaining
> that they wanted to prevent DB owners from installing trusted PLs, and
> I do recall people wishing that it didn't take superuser to install
> the other stuff.

If somebody were to complain about this, what could they complain
about? Potential complaints:

1. I'm the superuser and I don't want my DB owners to be able to
install extensions other than trusted PLs.
2. Or I want to control which specific ones they can install.
3. I'm a non-superuser DB owner and I want to delegate permissions to
install trusted extensions to some other user who is not a DB owner.

All of those sound reasonably legitimate; against that, you can always
argue that permissions should be more finely grained, and it's not
always worth the implementation effort to make it possible. On #1, I
tend to think that *most* people would be happy rather than sad about
DB owners being able to install extensions; after all, evil extensions
can be restricted by removing them from the disk (or marking them
untrusted), and most people who set up a database are hoping it's
going to get used for something. But somebody might not like it,
especially if e.g. it turns out that one of our "trusted" extensions
has a horrible security vulnerability. On #2, I can certainly imagine
large providers having a view about which extensions they think are
safe enough for users to install that differs from ours, and if that
horrible security vulnerability materializes it sure would be nice to
be able to easily disable access to just that one. #3 seems less
likely to be an issue, but it's not unthinkable.

"GRANT INSTALL ON mydb" seems like it would solve #1 and #3. You could
grant a particular DB owner permission to install extensions, or not.
If you have them that power WITH GRANT OPTION, then they could
sub-delegate. It wouldn't do anything about #2; that would require
some more complex scheme.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> On Mon, Jan 6, 2020 at 1:27 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> After sleeping on it, I'm liking that idea; it's simple, and it
>> preserves the existing behavior that DB owners can install trusted PLs
>> without any extra permissions.  Now, if we follow this up by marking
>> most of contrib as trusted, we'd be expanding that existing privilege.
>> But I think that's all right: I don't recall anybody ever complaining
>> that they wanted to prevent DB owners from installing trusted PLs, and
>> I do recall people wishing that it didn't take superuser to install
>> the other stuff.

> If somebody were to complain about this, what could they complain
> about? Potential complaints:

> 1. I'm the superuser and I don't want my DB owners to be able to
> install extensions other than trusted PLs.
> 2. Or I want to control which specific ones they can install.
> 3. I'm a non-superuser DB owner and I want to delegate permissions to
> install trusted extensions to some other user who is not a DB owner.

Sure, but all of these seem to be desires for features that could be
added later.  As for #1, we could have that just by not taking the
next step of marking anything but the PLs trusted (something that is
going to happen anyway for v13, if this patch doesn't move faster).
#2 is not a feature that exists now, either; actually, the patch *adds*
it, to the extent that the superuser is willing to adjust extension
control files.  Likewise, #3 is not a feature that exists now.  Also,
the patch adds something that looks partly like #3, in that the
superuser could grant pg_install_trusted_extension with admin option
to database users who should be allowed to delegate it.  Perhaps that's
inadequate, but I don't see why we can't wait for complaints before
trying to design something that satisfies hypothetical use cases.

The facts that I'm worried about are that this is already the January
'fest, and if we don't want to ship v13 with python 2 as still the
preferred python, we need to not only get this patch committed but do
some less-than-trivial additional work (that hasn't even been started).
So I'm getting very resistant to requests for more features in this patch.
I think everything you're suggesting above could be tackled later,
when and if there's actual field demand for it.

> "GRANT INSTALL ON mydb" seems like it would solve #1 and #3.

It's not apparent to me that that's better, and it seems possible that
it's worse.  The fact that a DB owner could grant that privilege to
himself means that you might as well just have it on all the time.
Like a table owner's DML rights, it would only be useful to prevent
accidentally shooting yourself in the foot ... but who accidentally
issues CREATE EXTENSION?  And if they do (for an extension that
deserves to be marked trusted) what harm is done really?  Worst
case is you drop it again.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
> > On Mon, Jan 6, 2020 at 1:27 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >> After sleeping on it, I'm liking that idea; it's simple, and it
> >> preserves the existing behavior that DB owners can install trusted PLs
> >> without any extra permissions.  Now, if we follow this up by marking
> >> most of contrib as trusted, we'd be expanding that existing privilege.
> >> But I think that's all right: I don't recall anybody ever complaining
> >> that they wanted to prevent DB owners from installing trusted PLs, and
> >> I do recall people wishing that it didn't take superuser to install
> >> the other stuff.
>
> > If somebody were to complain about this, what could they complain
> > about? Potential complaints:
>
> > 1. I'm the superuser and I don't want my DB owners to be able to
> > install extensions other than trusted PLs.

I don't agree that this is actually a sensible use-case, so I'm not
really sure why we're discussing solutions to make it work.  It happens
to be how things work because pg_pltemplate exists before we had
extensions and we never went back and cleaned that up- but that's
exactly what we're trying to do here, and adding in a nice feature at
the same time.

> > 2. Or I want to control which specific ones they can install.

This is exactly what the 'trusted' bit is for, isn't it?  If you think
that we need something that's actually a permission matrix between roles
and specific extensions, that's a whole different level, certainly, and
I don't think anyone's asked for or contemplated such a need.

I do like the idea of having a way to install more-or-less all
extensions out on to the filesystem and then giving superusers an
ability to decide which ones are 'trusted' and which ones are not,
without having to hand-hack the control files.  I don't particularly
like using GUCs for that but I'm not sure what a better option looks
like and I'm not completely convinced we really need this.  If we really
go down this route (without resorting to GUCs or something) then we'd
need an additional catalog table that $someone is allowed to populate
through some kind of SQL and a whole lot of extra work and I definitely
don't think we need that right now.

> > 3. I'm a non-superuser DB owner and I want to delegate permissions to
> > install trusted extensions to some other user who is not a DB owner.

This is a use-case that I do think exists (or, at least, I'm a superuser
or a DB owner and I'd like to delegate that privilege to another user).

> Sure, but all of these seem to be desires for features that could be
> added later.

We can't very well add a default role in one release and then decide we
want to use the GRANT-privilege system in the next and remove it...

> As for #1, we could have that just by not taking the
> next step of marking anything but the PLs trusted (something that is
> going to happen anyway for v13, if this patch doesn't move faster).

Ugh.  I find that to be a pretty horrible result.  Yes, we could mark
extensions later as 'trusted' but that'd be another year..

> #2 is not a feature that exists now, either; actually, the patch *adds*
> it, to the extent that the superuser is willing to adjust extension
> control files.  Likewise, #3 is not a feature that exists now.  Also,
> the patch adds something that looks partly like #3, in that the
> superuser could grant pg_install_trusted_extension with admin option
> to database users who should be allowed to delegate it.  Perhaps that's
> inadequate, but I don't see why we can't wait for complaints before
> trying to design something that satisfies hypothetical use cases.

#3 from Robert's list certainly strikes me as a valid use-case and not
just hypothetical.

> The facts that I'm worried about are that this is already the January
> 'fest, and if we don't want to ship v13 with python 2 as still the
> preferred python, we need to not only get this patch committed but do
> some less-than-trivial additional work (that hasn't even been started).
> So I'm getting very resistant to requests for more features in this patch.
> I think everything you're suggesting above could be tackled later,
> when and if there's actual field demand for it.

Perhaps I'm wrong, but I wouldn't think changing this from a
default-role based approach over to a GRANT'able right using our
existing GRANT system would be a lot of work.  I agree that addressing
some of the use-cases proposed above could be a great deal of work but,
as I say above, I don't agree that we need to address all of them.

> > "GRANT INSTALL ON mydb" seems like it would solve #1 and #3.
>
> It's not apparent to me that that's better, and it seems possible that
> it's worse.  The fact that a DB owner could grant that privilege to
> himself means that you might as well just have it on all the time.

I agree that the DB owner should have that right by default, just like
they have any of the other DB-level rights that exist, just like how the
GRANT system in general works today.  If they remove it from themselves,
then that's on them and they won't be able to create extensions until
they GRANT it back to themselves.

I do *not* agree that this means we shouldn't have DB-level rights for
database owners and that we should just go hand-hack the system to have
explicit "is this the DB owner?" checks.  The suggestion you're making
here seems to imply we should go hack up the CREATE SCHEMA check to have
it see if the user is the DB owner and then allow it, instead of doing
our normal privilege checks, and I don't think that makes any sense.  I
definitely don't like the idea of having this privilege act in any more
special way than our other privileges (be it a default-role or a GRANT'd
privilege, though obviously I'm much happier with the latter than the
former for this case).

> Like a table owner's DML rights, it would only be useful to prevent
> accidentally shooting yourself in the foot ... but who accidentally
> issues CREATE EXTENSION?  And if they do (for an extension that
> deserves to be marked trusted) what harm is done really?  Worst
> case is you drop it again.

Why are we talking about adding code for this though?  The GRANT system
already handles all of this just fine and we rarely hear complaints from
people about it.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Stephen Frost <sfrost@snowman.net> writes:
> Perhaps I'm wrong, but I wouldn't think changing this from a
> default-role based approach over to a GRANT'able right using our
> existing GRANT system would be a lot of work.

Nobody has proposed a GRANT-based API that seems even close to
acceptable from where I sit.  A new privilege bit on databases
is not it, at least not unless it works completely unlike
any other privilege bit.  It's giving control to the DB owners,
not the superuser, and that seems like quite the wrong thing
for this purpose.

Or to put it another way: I think that the grantable role, which
ultimately is handed out by the superuser, is the primary permissions
API in this design.  The fact that DB owners effectively have that
same privilege is a wart for backwards-compatibility.  If we were
doing this from scratch, that wart wouldn't be there.  What you're
proposing is to make the wart the primary (indeed sole) permissions
control mechanism for extension installation, and that just seems
completely wrong.  Superusers would have effectively *no* say in
who gets to install trusted extensions, which is turning the whole
thing on its head I think; it's certainly not responding to either
of Robert's first two points.

If we were willing to break backwards compatibility, what I'd prefer
is to just have the grantable role, and to say that you have to grant
that to DB owners if you want them to be able to install PLs.  I'm
not sure how loud the howls would be if we did that, but it'd be a
lot cleaner than any of these other ideas.

> I do *not* agree that this means we shouldn't have DB-level rights for
> database owners and that we should just go hand-hack the system to have
> explicit "is this the DB owner?" checks.  The suggestion you're making
> here seems to imply we should go hack up the CREATE SCHEMA check to have
> it see if the user is the DB owner and then allow it, instead of doing
> our normal privilege checks, and I don't think that makes any sense.

Uh, what?  Nothing in what I'm proposing goes anywhere near the
permissions needed for CREATE SCHEMA.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Stephen Frost <sfrost@snowman.net> writes:
> > Perhaps I'm wrong, but I wouldn't think changing this from a
> > default-role based approach over to a GRANT'able right using our
> > existing GRANT system would be a lot of work.
>
> Nobody has proposed a GRANT-based API that seems even close to
> acceptable from where I sit.  A new privilege bit on databases
> is not it, at least not unless it works completely unlike
> any other privilege bit.  It's giving control to the DB owners,
> not the superuser, and that seems like quite the wrong thing
> for this purpose.

I'm seriously confused by this.  Maybe we need to step back for a moment
because there are things that already exist today that I don't think
we're really contemplating.

The first is this- ANYONE can create an extension in the system today,
if it's marked as superuser=false.  If anything, it seems like that's
probably too loose- certainly based on your contention that ONLY
superusers should wield such a power and that letting anyone else do so
is a right that a superuser must explicitly grant.

> Or to put it another way: I think that the grantable role, which
> ultimately is handed out by the superuser, is the primary permissions
> API in this design.  The fact that DB owners effectively have that
> same privilege is a wart for backwards-compatibility.  If we were
> doing this from scratch, that wart wouldn't be there.  What you're
> proposing is to make the wart the primary (indeed sole) permissions
> control mechanism for extension installation, and that just seems
> completely wrong.  Superusers would have effectively *no* say in
> who gets to install trusted extensions, which is turning the whole
> thing on its head I think; it's certainly not responding to either
> of Robert's first two points.

Superusers don't have any (direct) say in who gets to create schemas
either, yet we don't seem to have a lot of people complaining about it.
In fact, superusers don't have any say in who gets to create functions,
or operators, or tables, or indexes, or EXTENSIONS, either.  The fact is
that DB owners can *already* create most objects, including extensions,
in the DB system without the superuser being able to say anything about
it.

I really don't understand this hold-up when it comes to (trusted)
extensions.  Consider that, today, in many ways, PLs *are* the 'trusted'
extensions that DB owners are already allowed to install.  They're
libraries of C functions that are trusted to do things right and
therefore they can be allowed to be installed by DB owners.

If we had a generic way to have a C library declare that it only exposes
'trusted' C functions, would we deny users the ability to create those
functions in the database, when they can create functions in a variety
of other trusted languages?  Why would the fact that they're C
functions, in that case, make them somehow special?  That is, in fact,
*exactly* what's already going on with pltemplate and trusted languages.

Having trusted extensions is giving us exactly what pltemplate does, but
in a generic way where any C library (or whatever else) can be declared
as trusted, as defined by the extension framework around it, and
therefore able to be installed by DB owners.  Considering we haven't got
any kind of check in the system today around extension creation, itself,
this hardly seems like a large step to me- one could even argue that
maybe we should just let ANYONE create them, but I'm not asking for
that (in fact, I could probably be argued into agreeing to remove the
ability for $anyone to create non-superuser extensions today, if we
added this privilege...).

> If we were willing to break backwards compatibility, what I'd prefer
> is to just have the grantable role, and to say that you have to grant
> that to DB owners if you want them to be able to install PLs.  I'm
> not sure how loud the howls would be if we did that, but it'd be a
> lot cleaner than any of these other ideas.

If we can't come to agreement regarding using a regular GRANT'able
right, then I'd much rather break backwards compatibility than have such
a hacked up wart like this special case you're talking about for PLs.

> > I do *not* agree that this means we shouldn't have DB-level rights for
> > database owners and that we should just go hand-hack the system to have
> > explicit "is this the DB owner?" checks.  The suggestion you're making
> > here seems to imply we should go hack up the CREATE SCHEMA check to have
> > it see if the user is the DB owner and then allow it, instead of doing
> > our normal privilege checks, and I don't think that makes any sense.
>
> Uh, what?  Nothing in what I'm proposing goes anywhere near the
> permissions needed for CREATE SCHEMA.

I understand that- you're talking about just having this 'wart' for
CREATE EXTENSION and I don't agree with having the 'wart' at all.  To
start doing this for PLs would be completely inconsistent with the way
the rest of the privilege system works and that's not ok.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Mon, Jan 6, 2020 at 6:56 PM Stephen Frost <sfrost@snowman.net> wrote:
> The first is this- ANYONE can create an extension in the system today,
> if it's marked as superuser=false.  If anything, it seems like that's
> probably too loose- certainly based on your contention that ONLY
> superusers should wield such a power and that letting anyone else do so
> is a right that a superuser must explicitly grant.

I don't think this argument makes any sense. Sure, anyone can create
an extension with superuser=false, but so what? From a security point
of view, when you create such an extension, you are using your own
privileges to do things that you could do anyway. The interesting case
is creating an extension that requires superuser privileges, probably
because it's going to call C functions. The superuser can and must
have the last word regarding who is allowed to do such things, because
the superuser is equivalent to the OS user and any other user of the
system is not. The "tenants" of the database system can't be allowed
to use it for things which the "owner" does not wish to permit.

On Mon, Jan 6, 2020 at 6:26 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> If we were willing to break backwards compatibility, what I'd prefer
> is to just have the grantable role, and to say that you have to grant
> that to DB owners if you want them to be able to install PLs.  I'm
> not sure how loud the howls would be if we did that, but it'd be a
> lot cleaner than any of these other ideas.

That seems like a fine idea. Then the superuser has ultimate control,
and can decide which database owners they want to trust, and whether
they'd like the database owners to be able to subdelegate those
permissions. The only thing it doesn't do is give any control over
exactly which extensions can be installed by non-superusers, which
would be a really nice thing to have, especially if we're going to
significant expand the list of trusted extensions (something that I
think is, overall, quite a good idea). I accept Tom's argument that he
isn't obliged to add every related feature somebody might want just
because he's doing some work in this area, but not his contention that
the demand for such a feature is entirely hypothetical and the
suggestion that perhaps nobody will care anyway. I expect the reaction
to be along the lines of "hey, it's great that we can let DB owners do
this now, but it's really too bad that I can't blacklist
$SCARY_EXTENSION". I don't think that we'll be better off if this
entire proposal gets voted down for lack of that capability, but I
think it would be a really good thing to add.

FWIW, I don't really buy the argument that you can adjust the
extension control files to get out from under this problem.
Technically, that is true. But in practice, the extension control
files are provided by your packager, and you don't want to modify them
because then your packaging system will get grumpy with you. While
it's reasonable for the packaging to provide a tentative answer to the
question of what should be trusted, trust is ultimately a matter of
local policy, and that policy should be configured someplace that's
not managed by RPM.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Robert Haas (robertmhaas@gmail.com) wrote:
> On Mon, Jan 6, 2020 at 6:56 PM Stephen Frost <sfrost@snowman.net> wrote:
> > The first is this- ANYONE can create an extension in the system today,
> > if it's marked as superuser=false.  If anything, it seems like that's
> > probably too loose- certainly based on your contention that ONLY
> > superusers should wield such a power and that letting anyone else do so
> > is a right that a superuser must explicitly grant.
>
> I don't think this argument makes any sense. Sure, anyone can create
> an extension with superuser=false, but so what? From a security point
> of view, when you create such an extension, you are using your own
> privileges to do things that you could do anyway. The interesting case
> is creating an extension that requires superuser privileges, probably
> because it's going to call C functions. The superuser can and must
> have the last word regarding who is allowed to do such things, because
> the superuser is equivalent to the OS user and any other user of the
> system is not. The "tenants" of the database system can't be allowed
> to use it for things which the "owner" does not wish to permit.

What's the security issue from installing a trusted extension?

> On Mon, Jan 6, 2020 at 6:26 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > If we were willing to break backwards compatibility, what I'd prefer
> > is to just have the grantable role, and to say that you have to grant
> > that to DB owners if you want them to be able to install PLs.  I'm
> > not sure how loud the howls would be if we did that, but it'd be a
> > lot cleaner than any of these other ideas.
>
> That seems like a fine idea. Then the superuser has ultimate control,
> and can decide which database owners they want to trust, and whether
> they'd like the database owners to be able to subdelegate those
> permissions. The only thing it doesn't do is give any control over
> exactly which extensions can be installed by non-superusers, which
> would be a really nice thing to have, especially if we're going to
> significant expand the list of trusted extensions (something that I
> think is, overall, quite a good idea). I accept Tom's argument that he
> isn't obliged to add every related feature somebody might want just
> because he's doing some work in this area, but not his contention that
> the demand for such a feature is entirely hypothetical and the
> suggestion that perhaps nobody will care anyway. I expect the reaction
> to be along the lines of "hey, it's great that we can let DB owners do
> this now, but it's really too bad that I can't blacklist
> $SCARY_EXTENSION". I don't think that we'll be better off if this
> entire proposal gets voted down for lack of that capability, but I
> think it would be a really good thing to add.

Why would it be trusted if it's $SCARY_EXTENSION ...?  Why are we trying
to punt on solving for that question by installing a much more
complicated system here than is really necessary, just to avoid having
to make that decision?

If the extension is trusted, then there isn't a security issue with it,
and it isn't scary, by definition, imv, which negates these arguments
about making the right to install it have to be hand delegated by a
superuser and needing a system for managing who is allowed to install
what extension.

If these functions were to just be put into core (as many really should
be...), instead of being out in contrib, this whole issue also wouldn't
exist and everyone would be able to use the functions (at least, those
that we decide are safe for users to directly use- and with appropriate
privilege access over ones that aren't), without any "the superuser must
approve of this explicitly after installation" fuss.

> FWIW, I don't really buy the argument that you can adjust the
> extension control files to get out from under this problem.
> Technically, that is true. But in practice, the extension control
> files are provided by your packager, and you don't want to modify them
> because then your packaging system will get grumpy with you. While
> it's reasonable for the packaging to provide a tentative answer to the
> question of what should be trusted, trust is ultimately a matter of
> local policy, and that policy should be configured someplace that's
> not managed by RPM.

This I tend to agree with- hacking around with control files or other
files installed with extensions from RPMs isn't a great plan.

A possible alternative would be to have a *new* configuration file (not
part of the GUC system) which admins could hack up to specify who should
be allowed to install what extension.  Or we make that a catalog table
instead because, well, such things should probably be in the database
where we can have dependency management and validity checking...

On the other hand, having individual packages for different extensions
is a pretty handy way of letting an administrator decide if they want
that extension to be installed on their system or not.  That's a pain
for contrib because, really, all of those should really just be in core,
or not included if we aren't confident that they're safe to use.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Tue, Jan 7, 2020 at 1:17 PM Stephen Frost <sfrost@snowman.net> wrote:
> Why would it be trusted if it's $SCARY_EXTENSION ...?  Why are we trying
> to punt on solving for that question by installing a much more
> complicated system here than is really necessary, just to avoid having
> to make that decision?

I'm not convinced that whether or not something is trusted is an
altogether objective question. For instance, postgres_fdw probably
doesn't let you become the superuser, unless it has bugs. But it does
let you make network connections originating from the database host,
and somebody might reasonably want to restrict that in a
security-sensitive environment. But the same user might be totally OK
with a particular database owner installing citext.

> If these functions were to just be put into core (as many really should
> be...), instead of being out in contrib, this whole issue also wouldn't
> exist and everyone would be able to use the functions (at least, those
> that we decide are safe for users to directly use- and with appropriate
> privilege access over ones that aren't), without any "the superuser must
> approve of this explicitly after installation" fuss.

Well, I don't agree with the idea of moving everything into core, but
I think a good solution to the problem at hand will reduce the fuss
while allowing superusers to retain some control.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings!

* Robert Haas (robertmhaas@gmail.com) wrote:
> On Tue, Jan 7, 2020 at 1:17 PM Stephen Frost <sfrost@snowman.net> wrote:
> > Why would it be trusted if it's $SCARY_EXTENSION ...?  Why are we trying
> > to punt on solving for that question by installing a much more
> > complicated system here than is really necessary, just to avoid having
> > to make that decision?
>
> I'm not convinced that whether or not something is trusted is an
> altogether objective question.

How have we managed to have an answer to that question for all of the
languages that work with PG then..?  I feel like the answer is actually
pretty clear, at least if we view it in that light, and in the specific
case below, we are in agreement on which way it goes.

> For instance, postgres_fdw probably
> doesn't let you become the superuser, unless it has bugs. But it does
> let you make network connections originating from the database host,
> and somebody might reasonably want to restrict that in a
> security-sensitive environment. But the same user might be totally OK
> with a particular database owner installing citext.

Agreement!  Progress!  At least as it relates to, specifically,
postgres_fdw and about how non-superusers should have to be granted
something special to be allowed to make network connections.

Here's the thing though..  creating the extension isn't *really* (in our
permissions model anyway) what lets you create outbound connections-
it's creating a 'SERVER', and to be able to do that you need to have
USAGE rights on the FDW, which, normally, only a superuser can create.
The crux here is that the FDW is created as part of the extension
though.  As long as only superusers can create extensions, that's fine,
but when we allow others to do so, we come to an interesting question:

No matter how we end up allowing a non-superuser to create a trusted
extension, who should end up owning it and being able to modify it
and/or grant access to objects within it?

We don't currently have anything that prevents objects from an
extension from being modified by their owner, for example, and that
seems like a problem from where I'm sitting when you're talking about
having non-superusers creating objects that previously only superusers
could, and where the ownership-level rights on those objects would allow
that user to do things we generally don't feel an 'untrusted' user
should be able to.

Which basically leads to- in my mental model of this, the 'create
trusted extension' action would be kind of like a 'sudo apt install',
where the result is an extension that's installed as if a superuser did
install it and therefore it's owned by a superuser and the DB owner
can't go monkey around with any of the functions or tables or such
(unless allowed by the extension), beyond granting access (or not) to
the schema that the extension is installed into (which is actually more
than the 'sudo apt install' example above would probably let you do).
Further, that installation doesn't give the DB owner any more rights to
do things on the system than they already had.

Of course, there's the other option, which is to just agree that,
because of the way postgres_fdw works, it's gotta be marked as
untrusted.  I would ask though- are we really sure that we aren't ever
going to have any issues with functions in untrusted languages (or any
other objects) created by extensions being owned by non-superusers?

> > If these functions were to just be put into core (as many really should
> > be...), instead of being out in contrib, this whole issue also wouldn't
> > exist and everyone would be able to use the functions (at least, those
> > that we decide are safe for users to directly use- and with appropriate
> > privilege access over ones that aren't), without any "the superuser must
> > approve of this explicitly after installation" fuss.
>
> Well, I don't agree with the idea of moving everything into core, but
> I think a good solution to the problem at hand will reduce the fuss
> while allowing superusers to retain some control.

I don't actually mean everything, just to be clear, but a whole lot of
what's in contrib really could be in core with only a relatively modest
increase in the size of our base install/catalog (and, yes, I know some
people would complain about that, but in that case I'd argue that maybe
we should arrange to let them optionally not include those during the
build or something else because they're probably taking other steps to
minimize the size of PG on disk if they care that much..).

Having something like postgres_fdw installed as part of core would also
address the complications we have above regarding who owns it and who
gets to grant out access to it.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Tue, Jan 7, 2020 at 4:36 PM Stephen Frost <sfrost@snowman.net> wrote:
> Here's the thing though..  creating the extension isn't *really* (in our
> permissions model anyway) what lets you create outbound connections-
> it's creating a 'SERVER', and to be able to do that you need to have
> USAGE rights on the FDW, which, normally, only a superuser can create.
> The crux here is that the FDW is created as part of the extension
> though.  As long as only superusers can create extensions, that's fine,
> but when we allow others to do so, we come to an interesting question:
>
> No matter how we end up allowing a non-superuser to create a trusted
> extension, who should end up owning it and being able to modify it
> and/or grant access to objects within it?

Hmm.  Good question. But it's addressed in the documentation for the
patch Tom wrote, so I don't know why we need to discuss it de novo.
His answer seems pretty sensible and also happens to, I think, match
what you've written here.

> Of course, there's the other option, which is to just agree that,
> because of the way postgres_fdw works, it's gotta be marked as
> untrusted.  I would ask though- are we really sure that we aren't ever
> going to have any issues with functions in untrusted languages (or any
> other objects) created by extensions being owned by non-superusers?

But I don't see what the question of "who owns the objects?" has to do
with whether a superuser might want to allow some extensions to be
installed but not others. I think someone might want that, and if I
understand correctly, Tom thought so too when he wrote v1 of the
patch, because it had some capabilities along these lines. All I'm
doing is arguing that his first instinct was correct. And I'm not even
sure that you're disagreeing, since you seem to think that the
question of whether postgres_fdw ought to be marked trusted is
debatable. I'm really not sure what we're arguing about here.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Robert Haas (robertmhaas@gmail.com) wrote:
> On Tue, Jan 7, 2020 at 4:36 PM Stephen Frost <sfrost@snowman.net> wrote:
> > Here's the thing though..  creating the extension isn't *really* (in our
> > permissions model anyway) what lets you create outbound connections-
> > it's creating a 'SERVER', and to be able to do that you need to have
> > USAGE rights on the FDW, which, normally, only a superuser can create.
> > The crux here is that the FDW is created as part of the extension
> > though.  As long as only superusers can create extensions, that's fine,
> > but when we allow others to do so, we come to an interesting question:
> >
> > No matter how we end up allowing a non-superuser to create a trusted
> > extension, who should end up owning it and being able to modify it
> > and/or grant access to objects within it?
>
> Hmm.  Good question. But it's addressed in the documentation for the
> patch Tom wrote, so I don't know why we need to discuss it de novo.
> His answer seems pretty sensible and also happens to, I think, match
> what you've written here.

I would disagree about it matching what I wrote, but only because it
goes farther and lets the extension choose, which is even better.
Thanks for pointing that out, I had missed how that was addressed.

> > Of course, there's the other option, which is to just agree that,
> > because of the way postgres_fdw works, it's gotta be marked as
> > untrusted.  I would ask though- are we really sure that we aren't ever
> > going to have any issues with functions in untrusted languages (or any
> > other objects) created by extensions being owned by non-superusers?
>
> But I don't see what the question of "who owns the objects?" has to do
> with whether a superuser might want to allow some extensions to be
> installed but not others. I think someone might want that, and if I
> understand correctly, Tom thought so too when he wrote v1 of the
> patch, because it had some capabilities along these lines. All I'm
> doing is arguing that his first instinct was correct. And I'm not even
> sure that you're disagreeing, since you seem to think that the
> question of whether postgres_fdw ought to be marked trusted is
> debatable. I'm really not sure what we're arguing about here.

Here's the thing though- I *am* disagreeing, and that Tom had addressed
the ownership issue solidifies my feeling that the justification that's
been proposed for why a superuser might want to allow some extensions to
be installed but not others (beyond the "trustable" question that we are
proposing to address) isn't valid.

You raised the point regarding postgres_fdw and a DB owner being able to
run 'create extension postgres_fdw;' and to then make network
connections, but that's proven to be invalid because, assuming we make
postgres_fdw trustable, we will surely make the FDW itself that's
created be owned by the bootstrap superuser and therefore the DB owner
*couldn't* create such network connections- at least, now without an
additional step being taken by a superuser.  Further, it's pretty clear
to everyone *why* that additional step has to be taken for postgres_fdw.

So I come back to the questions that were raised up-thread but either
weren't answered or were done so with invalid points, as explained above
regarding postgres_fdw:

What's the security issue from installing a trusted extension?

Why would a $SCARY_EXTENSION be marked as trusted?

If there's no security issue, and no $SCARY_EXTENSION that's marked as
trusted, then why wouldn't a superuser be comfortable allowing a DB
owner to install a trusted extension into the DB they own?  The DB where
they can create any other trusted object from functions in trusted
languages to operators to schemas to tables and indexes and views and
all the others?  What is the superuser concerned about?  What do they
need to check before allowing this?  What's dangerous about allowing it?

Maybe it would help to say that I'm seeing the pattern here being
something along the lines of:

0) DBA owns prod database, but is not a superuser.
1) DBA decides they want $trusted_extension but it isn't installed
2) DBA submits a ticket to the infra team and says "please install this
   RPM on to this database server"
3) infra reviews that request and decides if they're ok with the RPM
4) infra resolves the ticket and installs the RPM,
5) DBA then runs 'create extension $trusted_extension;' and go about
   doing whatever it is they want to do with that extension.

The approach you're advocating for, assuming I've understood it
correctly, requires the infra team to also log in to the database as the
postgres superuser and then grant this role to the DBA, and I just don't
see the justification for that additional step, and I'm sure they're
going to be asking themselves "why do I need to do this..?  what power
is this granting?  why is this dangerous?  What about this isn't to be
trusted?"

To the question of "how do we know if the extension is trusted?" I
answer- how do we know a PL is trusted?  We have to evaluate it,
consider what it allows the user to do, see if it has appropriate checks
to make sure the user can't circumvent our privilege system and can't
break out of the 'box', as it were, and then decide on if it's trusted
or not.  Sure, if we mark it as trusted then we have to deal with fixing
any bugs that come up with it, including security issues, but that's
just the same as we do with the core code (and, for the most part, what
we do with the core extensions also anyway...  I can't think of a single
case off-hand where we have said "well, you have to actually install
that extension, so we aren't going to address this security issue that
you found in it"), so I don't see it as being that difficult for us to
also do that for extensions.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Tue, Jan 7, 2020 at 7:32 PM Stephen Frost <sfrost@snowman.net> wrote:
> You raised the point regarding postgres_fdw and a DB owner being able to
> run 'create extension postgres_fdw;' and to then make network
> connections, but that's proven to be invalid because, assuming we make
> postgres_fdw trustable, we will surely make the FDW itself that's
> created be owned by the bootstrap superuser and therefore the DB owner
> *couldn't* create such network connections- at least, now without an
> additional step being taken by a superuser.  Further, it's pretty clear
> to everyone *why* that additional step has to be taken for postgres_fdw.

To me, this seems more accidental than the natural fallout of good design.

> Why would a $SCARY_EXTENSION be marked as trusted?

Well, again, my point in using postgres_fdw as an example was not that
it should be untrusted, or that it should be trusted, but that
different people might have different views about that question, and
therefore configurability would be good. I believe the same thing
applies in other cases. For me, this boils down to the view that the
superuser can have arbitrary preferences about what C code they want
to let users run, and they need not justify such views with reference
to anything in particular. Some superuser can decide that they think
hstore is great stuff but bloom is too experimental and isn is a pile
of crap, and that all seems perfectly legitimate to me. And some other
superuser can have a different view and that seems fine, too. I can't
think of any reason why a particular installation should have to
decide between certifying most of contrib and certifying none of it,
with no intermediate options. I guess you see it differently.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Robert Haas (robertmhaas@gmail.com) wrote:
> On Tue, Jan 7, 2020 at 7:32 PM Stephen Frost <sfrost@snowman.net> wrote:
> > You raised the point regarding postgres_fdw and a DB owner being able to
> > run 'create extension postgres_fdw;' and to then make network
> > connections, but that's proven to be invalid because, assuming we make
> > postgres_fdw trustable, we will surely make the FDW itself that's
> > created be owned by the bootstrap superuser and therefore the DB owner
> > *couldn't* create such network connections- at least, now without an
> > additional step being taken by a superuser.  Further, it's pretty clear
> > to everyone *why* that additional step has to be taken for postgres_fdw.

(guessing it was clear, but sorry for the typo above, 'now' should have
been 'not')

> To me, this seems more accidental than the natural fallout of good design.

I disagree that the privilege design for FDWs was accidental.  Perhaps
it was a mistake to automatically run the CREATE FDW as part of the
extension script, but that doesn't really change any of the argument I'm
making.

> > Why would a $SCARY_EXTENSION be marked as trusted?
>
> Well, again, my point in using postgres_fdw as an example was not that
> it should be untrusted, or that it should be trusted, but that
> different people might have different views about that question, and
> therefore configurability would be good. I believe the same thing
> applies in other cases. For me, this boils down to the view that the
> superuser can have arbitrary preferences about what C code they want
> to let users run, and they need not justify such views with reference
> to anything in particular. Some superuser can decide that they think
> hstore is great stuff but bloom is too experimental and isn is a pile
> of crap, and that all seems perfectly legitimate to me. And some other
> superuser can have a different view and that seems fine, too. I can't
> think of any reason why a particular installation should have to
> decide between certifying most of contrib and certifying none of it,
> with no intermediate options. I guess you see it differently.

What I see differently is that the purview of those decisions should be
that of the DB owner and not the superuser.  If the superuser really
wants to own those decisions, then they should be the DB owner too, and
then they can also control what schemas exist and who can use them, or
they can GRANT that right out only to whomever they trust with it, and
likewise could GRANT out this install-extension right out to whomever
they deem worthy.

As it relates to things in contrib that could be classified as 'a pile
of crap' or 'too experimental'- that's *our* failing, and one which we
should accept and address instead of punting on it.  In my recollection,
this is far from the first time someone's suggested that maybe we should
try to clean up contrib.  I don't accept that our reluctance to punt
things out of contrib that shouldn't be there is an acceptable argument
against using our existing GRANT system for this new right or sufficient
justification to use a default role instead.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Wed, Jan 8, 2020 at 6:09 PM Stephen Frost <sfrost@snowman.net> wrote:
> > To me, this seems more accidental than the natural fallout of good design.
>
> I disagree that the privilege design for FDWs was accidental.

That seems like a stronger statement than the one I made...

> What I see differently is that the purview of those decisions should be
> that of the DB owner and not the superuser.

Yeah, I don't agree with that at all. If I'm a hosting provider, I
want to tell my customers what they're allowed to run and what they're
not allowed to run, but I don't want them to have to call me when they
want to run an extension I've decided is OK.

> As it relates to things in contrib that could be classified as 'a pile
> of crap' or 'too experimental'- that's *our* failing, and one which we
> should accept and address instead of punting on it.

I don't think changing what's in contrib helps much. Even if we rm
-rf'd it, there's the same problem with out-of-core extensions. Joe
Extensionman may think his extension ought to be trusted, and package
it as such, but Paula Skepticaldba is entitled to think Joe's view of
the security risks originating from his code is overly rosy.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Robert Haas (robertmhaas@gmail.com) wrote:
> On Wed, Jan 8, 2020 at 6:09 PM Stephen Frost <sfrost@snowman.net> wrote:
> > > To me, this seems more accidental than the natural fallout of good design.
> >
> > I disagree that the privilege design for FDWs was accidental.
>
> That seems like a stronger statement than the one I made...

Perhaps I misunderstood what you were referring to then.

> > What I see differently is that the purview of those decisions should be
> > that of the DB owner and not the superuser.
>
> Yeah, I don't agree with that at all. If I'm a hosting provider, I
> want to tell my customers what they're allowed to run and what they're
> not allowed to run, but I don't want them to have to call me when they
> want to run an extension I've decided is OK.

Now I'm really floored because I've also been contemplating exactly the
situation of a hosting provider.  In my experience, there's a few pretty
common themes among the ones that I've played with:

- Users definitely don't get true PG superuser
- They certainly like users to be able to install trusted extensions
- They'd generally prefer to minimize the size of their fork

I've been feeling like the solution I'm pushing for would, in the end,
*reduce* the amount of code they have that's different from PG, which I
believe they'd see as an entirely good thing.  The approach proposed in
this thread seems likely to either increase the size of the fork or, at
best, be about the same size.

Now the question boils down to "what's trusted?" and if I'm a hosting
provider, it'd sure be nice to foist off the responsibility of figuring
that out on a community that I can trust that will do so at no cost to
me, and who will address any bugs or security holes in those trusted
extensions for me.

Realistically, we pretty much already do that for contrib and that's
really all that's relevant here- anything else would need to be
physically installed on the system in order for a user to be able to
install it, and I expect hosting providers to be pretty happy with a
solution that boils down to just having to install RPMs, or not (or the
equivilant with regard to putting files into the right places and such).

Now, if we're talking about defining repositories or allowing users to
upload their *own* C code from their systems in to PG and install that
as an extension, then, sure, that's gotta be limited to a superuser (and
wouldn't be allowed by hosting providers).  If the extension is
installed on the system though, and it's marked as trusted, then why do
we need a superuser to take some additional action to allow it to be
installed..?

When it comes to the hosting provider case, I'd argue that what we're
missing here is a way for them to give their "not-quite-a-superuser
role" the ability to have certain capabilities (at install time, of
course)- a good example of that would be "allowed to make outbound
network connections", with a way to then be able to delegate out that
ability to others.  Then they'd actually be able to use things like
postgres_fdw and dblink after they're installed and without having to
get a superuser to grant them that ability post-install (and that order
of operations issue is a pretty key one- it's far simpler to set things
up and hand them over to the user than to have some operation that has
to happen as an actual superuser after the installation is done).

Even if we marked postgres_fdw as trusted, and used this default-role
based approach, we're almost certainly going to have the FDW itself be
owned by the bootstrap superuser and therefore whatever non-superuser
role that installs it wouldn't be able to actually use it without some
other changes.  I'd love to get to a point where you could have an
initially set up system with a not-quite-superuser role that would be
able to actually install AND use postgres_fdw, without having to fork
PG.

> > As it relates to things in contrib that could be classified as 'a pile
> > of crap' or 'too experimental'- that's *our* failing, and one which we
> > should accept and address instead of punting on it.
>
> I don't think changing what's in contrib helps much. Even if we rm
> -rf'd it, there's the same problem with out-of-core extensions. Joe
> Extensionman may think his extension ought to be trusted, and package
> it as such, but Paula Skepticaldba is entitled to think Joe's view of
> the security risks originating from his code is overly rosy.

Out of core extensions have to get installed on to the system though,
they don't just show up magically, and lots and lots of folks out there
from corporate infrastructure groups to hosting providers have got lots
of experience with deciding what they'll allow to be installed on a
system and what they won't, what repositories of code they'll trust and
which they won't.

Of course, when it comes to contrib extensions, if we don't feel
comfortable with them and we don't want to spend the time to vet them,
we can certainly just leave them marked as 'untrusted' and tell anyone
who wants to make them trusted that they need to put in the effort to
review that extension and get everyone comfortable with it.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Thu, Jan 9, 2020 at 10:09 AM Stephen Frost <sfrost@snowman.net> wrote:
> [ wall of text ]

I don't see anything in here I really disagree with, but nor do I
understand why any of it means that giving superusers the ability to
customize which extensions are database-owner-installable would be a
bad thing.

> > I don't think changing what's in contrib helps much. Even if we rm
> > -rf'd it, there's the same problem with out-of-core extensions. Joe
> > Extensionman may think his extension ought to be trusted, and package
> > it as such, but Paula Skepticaldba is entitled to think Joe's view of
> > the security risks originating from his code is overly rosy.
>
> Out of core extensions have to get installed on to the system though,
> they don't just show up magically, and lots and lots of folks out there
> from corporate infrastructure groups to hosting providers have got lots
> of experience with deciding what they'll allow to be installed on a
> system and what they won't, what repositories of code they'll trust and
> which they won't.

You seem to be ignoring the actual point of that example, which is
that someone may want to install the extension but have a different
view than the packager about whether it should be trusted.

You seem to think that that hosting providers and system
administrators will be thrilled to accept the judgement of developers
about which extensions should be trusted in their environment. Great!
I'm not trying to take away their ability to accept the judgement of
developers on that question. However, I also think some people will
want more control.

Evidently you disagree, and that's fine, even if I don't understand
why. Given some of the development projects you've done in the past, I
find it extremely surprising to here you now taking the position that
fine-grained security controls are, in this case, unnecessary and
useless, but you don't have to like it everywhere just because you
like it for some things.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Robert Haas (robertmhaas@gmail.com) wrote:
> On Thu, Jan 9, 2020 at 10:09 AM Stephen Frost <sfrost@snowman.net> wrote:
> > [ wall of text ]

This really isn't helpful.

> I don't see anything in here I really disagree with, but nor do I
> understand why any of it means that giving superusers the ability to
> customize which extensions are database-owner-installable would be a
> bad thing.

Alright, I think there's definitely something we need to sort through.

If you agree that the approach I advocated means less code for hosting
providers to have to change in their fork, and that they don't want to
give users superuser, and that they want non-superusers to be able to
install extensions, and that they really don't want to modify things
post-install, then I don't get why you're against the DB-level privilege
that I've been advocating for except that it's "not as customizable."

Are you saying that in order to have something here that we must make it
so that a superuser is able to specifiy, individually, which extensions
can be installed by which users?  You keep coming back to this point of
saying that you want this to be 'customizable' but I really don't see
any justification for the level of customization you're asking for- but
I see an awful lot of work involved.  When there's a lot of work
involved for a use-case that no one is actually asking for, I'm really
skeptical.  The use-cases that you've presented, at least thus far,
certainly haven't swayed me into thinking that you're right that there's
a justifiable use-case here for this level of complicated privileges.

I'm also not convinced that such a design would even be practical- we
don't know all of the extensions that a given PG install will be able to
have when it's first installed.  If postgis isn't on the filesystem when
someone installs PG, how do I, as a superuser, say that $user is allowed
to install postgis?  Or do we always have to have this two-step "install
on filesystem", "grant privs to $user to install" process?  What if that
extension is then uninstalled from the filesystem?  Do we have to clean
up the GRANT that was done?

> > > I don't think changing what's in contrib helps much. Even if we rm
> > > -rf'd it, there's the same problem with out-of-core extensions. Joe
> > > Extensionman may think his extension ought to be trusted, and package
> > > it as such, but Paula Skepticaldba is entitled to think Joe's view of
> > > the security risks originating from his code is overly rosy.
> >
> > Out of core extensions have to get installed on to the system though,
> > they don't just show up magically, and lots and lots of folks out there
> > from corporate infrastructure groups to hosting providers have got lots
> > of experience with deciding what they'll allow to be installed on a
> > system and what they won't, what repositories of code they'll trust and
> > which they won't.
>
> You seem to be ignoring the actual point of that example, which is
> that someone may want to install the extension but have a different
> view than the packager about whether it should be trusted.

Why would someone want to install something that isn't trusted?  You're
implying that's what is happening here, but it doesn't make any sense to
me and without it making sense I can't agree that it's a sensible enough
use-case to demand a lot of work be put into it.

> You seem to think that that hosting providers and system
> administrators will be thrilled to accept the judgement of developers
> about which extensions should be trusted in their environment. Great!

Huh?  Hosting providers are the ones that choose what gets installed on
the filesystem, certainly not developers, so I am baffled how you came
to the conclusion that I'm suggesting administrators are trusting the
judgement of developers.  That's just not at all the case.

> Evidently you disagree, and that's fine, even if I don't understand
> why. Given some of the development projects you've done in the past, I
> find it extremely surprising to here you now taking the position that
> fine-grained security controls are, in this case, unnecessary and
> useless, but you don't have to like it everywhere just because you
> like it for some things.

I'm all for fine-grained control- where it makes sense.  I'm still
*very* much of the opinion that we should be able to let DB owners and
schema owners control what kind of objects users are allowed to create
in their DBs/schemas.  I want a "GRANT CREATE FUNCTION ON SCHEMA mine TO
you;" ability.  I'm not clamouring for a way to say "GRANT CREATE
THISSPECIFICFUNCTION ON SCHEMA mine TO you;" or something like "GRANT
CREATE FUNCTION MATCHING REGEXP 'abc_*' ON SCHEMA mine TO you;".

In the end, superusers are, in fact, the ones who grant out ALL access,
the question is what level of access they want to grant out and to whom.
When it comes to trusted objects, we've historically said that it's the
DB owner who gets to say who can grant out that access and all I'm
trying to argue for is that we continue with that.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Thu, Jan 9, 2020 at 11:30 AM Stephen Frost <sfrost@snowman.net> wrote:
> * Robert Haas (robertmhaas@gmail.com) wrote:
> > On Thu, Jan 9, 2020 at 10:09 AM Stephen Frost <sfrost@snowman.net> wrote:
> > > [ wall of text ]
>
> This really isn't helpful.

Sorry.

That being said, I'm pretty tired of writing emails that say the same
thing over and over again and having you write long responses that
don't seem to actually respond to the points being raised in the
email.

Like:

> > I don't see anything in here I really disagree with, but nor do I
> > understand why any of it means that giving superusers the ability to
> > customize which extensions are database-owner-installable would be a
> > bad thing.
>
> Alright, I think there's definitely something we need to sort through.
>
> If you agree that the approach I advocated means less code for hosting
> providers to have to change in their fork, and that they don't want to
> give users superuser, and that they want non-superusers to be able to
> install extensions, and that they really don't want to modify things
> post-install, then I don't get why you're against the DB-level privilege
> that I've been advocating for except that it's "not as customizable."

What I was writing about in the quoted paragraph and what you are
writing about in the response are two different things. I said
*nothing* about a DB-level privilege in the paragraph I wrote, and yet
somehow your response to that paragraph says that I'm opposing a
DB-level privilege.

> Are you saying that in order to have something here that we must make it
> so that a superuser is able to specifiy, individually, which extensions
> can be installed by which users?  You keep coming back to this point of
> saying that you want this to be 'customizable' but I really don't see
> any justification for the level of customization you're asking for- but
> I see an awful lot of work involved.  When there's a lot of work
> involved for a use-case that no one is actually asking for, I'm really
> skeptical.  The use-cases that you've presented, at least thus far,
> certainly haven't swayed me into thinking that you're right that there's
> a justifiable use-case here for this level of complicated privileges.

I set forth my exact views in
http://postgr.es/m/CA+TgmoZK=5EC2O13J3sfOUCvYtvjGtxUKg=wQ11Q-wy4sc4b+g@mail.gmail.com

Everything since then has been trying to somehow clarify what I wrote
in that email, which has resulted in me repeating everything I said
there several times in different words. I would like to stop doing
that now. It appears to be clarifying nothing, failing to advance the
patch, and irritating you.

> I'm also not convinced that such a design would even be practical- ...

Again, as I said upthread, Tom had the exact feature about which I am
talking in the first version of the patch. That is a strong argument
in favor of it being practical. It's also a pretty good argument that
it is at least potentially useful, because Tom doesn't usually do
useless things for no reason.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> Again, as I said upthread, Tom had the exact feature about which I am
> talking in the first version of the patch. That is a strong argument
> in favor of it being practical. It's also a pretty good argument that
> it is at least potentially useful, because Tom doesn't usually do
> useless things for no reason.

To try to clarify that a bit: I think there is certainly some value
in allowing superusers to control which extensions could be installed
by non-superusers, further restricting what we may think is trustworthy.

However, I felt at the time that my GUC-based implementation of that
was ugly, and then Peter raised some concrete points against it,
so I took it out.  I don't want to put it back in the same form.
I think we could leave designing a replacement for later, because it's
pretty optional, especially if we aren't aggressive about promoting
contrib modules to "trusted" status.  I don't agree that the lack of
such a feature is a reason not to commit what I've got.

In any case, AFAICT most of the heat-vs-light in this thread has not
been about which extensions are trustworthy, but about which users
should be allowed to install extensions, which seems like a totally
independent discussion.  And controlling that is also a feature that
we don't have today, so I'd rather get a minimal feature committed
for v13 and then later consider whether we need more functionality.

The idea of a DB-level INSTALL privilege addresses the second
point not the first, unless I'm totally misunderstanding it.  As
I said before, I'm not terribly comfortable with handing control
of that over to non-superuser DB owners, and I sure don't see why
doing so should be a required part of the minimal feature.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Thu, Jan 9, 2020 at 1:35 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
> > Again, as I said upthread, Tom had the exact feature about which I am
> > talking in the first version of the patch. That is a strong argument
> > in favor of it being practical. It's also a pretty good argument that
> > it is at least potentially useful, because Tom doesn't usually do
> > useless things for no reason.
>
> To try to clarify that a bit: I think there is certainly some value
> in allowing superusers to control which extensions could be installed
> by non-superusers, further restricting what we may think is trustworthy.

Cool.

> However, I felt at the time that my GUC-based implementation of that
> was ugly, and then Peter raised some concrete points against it,
> so I took it out.  I don't want to put it back in the same form.
> I think we could leave designing a replacement for later, because it's
> pretty optional, especially if we aren't aggressive about promoting
> contrib modules to "trusted" status.

Agreed.

> I don't agree that the lack of
> such a feature is a reason not to commit what I've got.

I said the same in
http://postgr.es/m/CA+TgmoYgwgS_RnMOooczZCgrZFqtfngshAq2Gu7LM5SKxrf_xQ@mail.gmail.com
- penultimate paragraph, last sentence.

> In any case, AFAICT most of the heat-vs-light in this thread has not
> been about which extensions are trustworthy, but about which users
> should be allowed to install extensions, which seems like a totally
> independent discussion.

I agree it's independent. It wasn't really the main point of what *I*
was trying to talk about, but the heat-vs-light problem seems to have
totally obscured what I *was* trying to talk about.

> And controlling that is also a feature that
> we don't have today, so I'd rather get a minimal feature committed
> for v13 and then later consider whether we need more functionality.
>
> The idea of a DB-level INSTALL privilege addresses the second
> point not the first, unless I'm totally misunderstanding it.  As
> I said before, I'm not terribly comfortable with handing control
> of that over to non-superuser DB owners, and I sure don't see why
> doing so should be a required part of the minimal feature.

So, if I understand correctly, the patch you are proposing to commit
has a new system role, and if you've got that system role, then you
can install extensions. I thought that part of the earlier debate was
whether DB owners should also be able to install trusted extensions
even without that role, and I thought it would be cleaner if the
answer was "no," because then the superuser could decide whether to
grant that role or not in particular cases. But I'm not clear whether
you agreed with that, what Stephen thought about it, or whether that's
still what you are proposing to commit.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> So, if I understand correctly, the patch you are proposing to commit
> has a new system role, and if you've got that system role, then you
> can install extensions.

Install *trusted* extensions, correct.  The patch as it stands also
allows DB owners to install trusted extensions.

> I thought that part of the earlier debate was
> whether DB owners should also be able to install trusted extensions
> even without that role, and I thought it would be cleaner if the
> answer was "no," because then the superuser could decide whether to
> grant that role or not in particular cases. But I'm not clear whether
> you agreed with that, what Stephen thought about it, or whether that's
> still what you are proposing to commit.

I agree that if we dropped the proviso about DB owners, it would be
a cleaner design.  I included that only for backwards compatibility
with the existing behavior that DB owners can install trusted PLs.
If we can agree that we're willing to lose that behavior, I'd be
perfectly fine with removing the special case for DB owners.
However, I'm unsure whether that compatibility cost is acceptable.
It's definitely likely that it would cause an upgrade headache
for some installations.

One idea for working around the upgrade problem would be to teach
pg_dumpall to automatically issue "GRANT pg_install_trusted_extension"
to each DB-owner role, when dumping from a pre-v13 database.  There's
room to object to that, because it would end with more privilege than
before (that is, an owner of some DB could now install extensions
even in DBs she doesn't own, as long as she can connect to them).
So maybe it's a bad idea.  But it would probably reduce the number
of complaints --- and I think a lot of installations would end up
making such grants anyway, because otherwise their DB owners can't
do things they expect to be able to do.

I should not put words into Stephen's mouth, but perhaps his
concern about having some DB-level privilege here is to alleviate
the problem that there's no exact equivalent to the old level of
privilege that DB ownership afforded, ie you can install in your
own DB but not others.  It's not clear to me whether that behavior
is critical to preserve.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Robert Haas (robertmhaas@gmail.com) wrote:
> On Thu, Jan 9, 2020 at 1:35 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > Robert Haas <robertmhaas@gmail.com> writes:
> > > Again, as I said upthread, Tom had the exact feature about which I am
> > > talking in the first version of the patch. That is a strong argument
> > > in favor of it being practical. It's also a pretty good argument that
> > > it is at least potentially useful, because Tom doesn't usually do
> > > useless things for no reason.
> >
> > To try to clarify that a bit: I think there is certainly some value
> > in allowing superusers to control which extensions could be installed
> > by non-superusers, further restricting what we may think is trustworthy.
>
> Cool.

I'm arguing for the position that superusers/admins have the ability to
control which extensions exist on the filesystem, and that plus the
'trusted' marking is sufficient flexibility.

> > However, I felt at the time that my GUC-based implementation of that
> > was ugly, and then Peter raised some concrete points against it,
> > so I took it out.  I don't want to put it back in the same form.
> > I think we could leave designing a replacement for later, because it's
> > pretty optional, especially if we aren't aggressive about promoting
> > contrib modules to "trusted" status.
>
> Agreed.

Also agreed- which is why I figured we weren't really discussing that
any more.

> > I don't agree that the lack of
> > such a feature is a reason not to commit what I've got.
>
> I said the same in
> http://postgr.es/m/CA+TgmoYgwgS_RnMOooczZCgrZFqtfngshAq2Gu7LM5SKxrf_xQ@mail.gmail.com
> - penultimate paragraph, last sentence.

I also agree that we don't need the "who can install what extension"
flexibility that the original GUC-based approach contemplated, but
that's because I don't think we are likely to ever need it.  If we do
and someone comes up with a good design for it, that'd be fine too.

> > In any case, AFAICT most of the heat-vs-light in this thread has not
> > been about which extensions are trustworthy, but about which users
> > should be allowed to install extensions, which seems like a totally
> > independent discussion.
>
> I agree it's independent. It wasn't really the main point of what *I*
> was trying to talk about, but the heat-vs-light problem seems to have
> totally obscured what I *was* trying to talk about.

I'm entirely confused about what you were trying to talk about then.

Most of the back-and-forth, as I saw it anyway, were points being raised
to say "we can't let the right of installing extensions be allowed to DB
owners", which I don't agree with and which I've yet to see an actual
justification for beyond "well, we think it should require some explicit
superuser privilege-granting, beyond the granting that the superuser
does when they create a database owned by a given user."

> > And controlling that is also a feature that
> > we don't have today, so I'd rather get a minimal feature committed
> > for v13 and then later consider whether we need more functionality.
> >
> > The idea of a DB-level INSTALL privilege addresses the second
> > point not the first, unless I'm totally misunderstanding it.  As
> > I said before, I'm not terribly comfortable with handing control
> > of that over to non-superuser DB owners, and I sure don't see why
> > doing so should be a required part of the minimal feature.
>
> So, if I understand correctly, the patch you are proposing to commit
> has a new system role, and if you've got that system role, then you
> can install extensions. I thought that part of the earlier debate was
> whether DB owners should also be able to install trusted extensions
> even without that role, and I thought it would be cleaner if the
> answer was "no," because then the superuser could decide whether to
> grant that role or not in particular cases. But I'm not clear whether
> you agreed with that, what Stephen thought about it, or whether that's
> still what you are proposing to commit.

I do *not* agree with having a default role for this, at all.  This
looks just like the right to CREATE tables or functions inside a schema,
except with a DB-level object (an extension) instead of a schema-level
object, and that is the purview of the DB owner.

The arguments raised about $SCARYEXTENSION and security concerns make a
lot of sense- I agree that those are things we should discuss and make
sure that allowing a DB owner this privilege won't pave a way for them
to get superuser access, but, imv anyway, we discussed those and didn't
actually come up with any cases where it'd be an issue, in part thanks
to Tom's design where the objects end up owned by the bootstrap
superuser except in specific cases.

So I'm at a loss for why there is this insistence on a default role and
a superuser-explicit-granting based approach that goes beyond "is it
installed on the filesystem?" and "is it marked as trusted?".

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Thu, Jan 9, 2020 at 2:48 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I agree that if we dropped the proviso about DB owners, it would be
> a cleaner design.  I included that only for backwards compatibility
> with the existing behavior that DB owners can install trusted PLs.
> If we can agree that we're willing to lose that behavior, I'd be
> perfectly fine with removing the special case for DB owners.
> However, I'm unsure whether that compatibility cost is acceptable.
> It's definitely likely that it would cause an upgrade headache
> for some installations.

I was assuming that installing extensions was fairly infrequent and
that it probably gets done mostly by superusers anyway, so probably
most people won't care if, after upgrading, they needed an extra GRANT
to get things working again. That might be wrong, though.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

On Thu, Jan 9, 2020 at 14:48 Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
> So, if I understand correctly, the patch you are proposing to commit
> has a new system role, and if you've got that system role, then you
> can install extensions.

Install *trusted* extensions, correct.  The patch as it stands also
allows DB owners to install trusted extensions.

> I thought that part of the earlier debate was
> whether DB owners should also be able to install trusted extensions
> even without that role, and I thought it would be cleaner if the
> answer was "no," because then the superuser could decide whether to
> grant that role or not in particular cases. But I'm not clear whether
> you agreed with that, what Stephen thought about it, or whether that's
> still what you are proposing to commit.

I agree that if we dropped the proviso about DB owners, it would be
a cleaner design.  I included that only for backwards compatibility
with the existing behavior that DB owners can install trusted PLs.
If we can agree that we're willing to lose that behavior, I'd be
perfectly fine with removing the special case for DB owners.
However, I'm unsure whether that compatibility cost is acceptable.
It's definitely likely that it would cause an upgrade headache
for some installations.

One idea for working around the upgrade problem would be to teach
pg_dumpall to automatically issue "GRANT pg_install_trusted_extension"
to each DB-owner role, when dumping from a pre-v13 database.  There's
room to object to that, because it would end with more privilege than
before (that is, an owner of some DB could now install extensions
even in DBs she doesn't own, as long as she can connect to them).
So maybe it's a bad idea.  But it would probably reduce the number
of complaints --- and I think a lot of installations would end up
making such grants anyway, because otherwise their DB owners can't
do things they expect to be able to do.

I should not put words into Stephen's mouth, but perhaps his
concern about having some DB-level privilege here is to alleviate
the problem that there's no exact equivalent to the old level of
privilege that DB ownership afforded, ie you can install in your
own DB but not others.  It's not clear to me whether that behavior
is critical to preserve.

I am not particularly concerned about that backwards compatibility issue and I don’t intend to base my argument on it, but I would use that case to point out that we have long had the ability to install trusted C functions into the backend as a DB owner, without complaint from either users or security pedants, and that backs up my position that we are setting up this privilege at the wrong level by using a default role which a superuser must grant independently from DB ownership. 

Thanks,

Stephen

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Stephen Frost <sfrost@snowman.net> writes:
> So I'm at a loss for why there is this insistence on a default role and
> a superuser-explicit-granting based approach that goes beyond "is it
> installed on the filesystem?" and "is it marked as trusted?".

Okay, so it seems like we're down to just this one point of contention.
You feel that the superuser can control what is in the extension library
directory and that that ought to be sufficient control.  I disagree
with that, for two reasons:

* ISTM that that's assuming that the DBA and the sysadmin are the same
person (or at least hold identical views on this subject).  In many
installations it'd only be root who has control over what's in that
directory, and I don't think it's unreasonable for the DBA to wish
to be able to exercise additional filtering.

* The point of a default role would be for the DBA to be able to
control which database users can install extensions.  Even if the
DBA has full authority over the extension library, that would not
provide control over who can install, only over what is available
for any of them to install.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Stephen Frost <sfrost@snowman.net> writes:
> I am not particularly concerned about that backwards compatibility issue
> and I don’t intend to base my argument on it, but I would use that case to
> point out that we have long had the ability to install trusted C functions
> into the backend as a DB owner, without complaint from either users or
> security pedants,

Right, which is why my patch proposes generalizing that feature for
trusted PLs into a general feature for other extensions.  I'd be
much leerier of that if we'd had any pushback on it for trusted PLs.

> ... and that backs up my position that we are setting up this
> privilege at the wrong level by using a default role which a superuser must
> grant independently from DB ownership.

Don't see how this follows.  It's somewhat accidental I think that
the existing behavior is tied to DB ownership.  That's just because
at the time, that's the only sort of privilege we had that seemed
intermediate between superuser and Joe User.  If we were designing
the behavior today, with default roles already a done deal for
handing out possibly-dangerous privileges, I think there's no
question that we'd be setting up this privilege as a default role
rather than tying it to DB ownership.  We don't make DB ownership
a prerequisite to creating other sorts of functions, yet other
functions can be just as dangerous in some cases as C functions.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Thu, Jan 9, 2020 at 3:18 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> * ISTM that that's assuming that the DBA and the sysadmin are the same
> person (or at least hold identical views on this subject).  In many
> installations it'd only be root who has control over what's in that
> directory, and I don't think it's unreasonable for the DBA to wish
> to be able to exercise additional filtering.

An emphatic +1 from me. This is what I've been trying to argue over
and over, apparently rather unclearly.

> * The point of a default role would be for the DBA to be able to
> control which database users can install extensions.  Even if the
> DBA has full authority over the extension library, that would not
> provide control over who can install, only over what is available
> for any of them to install.

I agree with that, too. I guess you could decide that the answer to
the question "who can install extensions?" must be the same as the
answer to the question "who owns a database?" but having the
flexibility to make the answers to those questions different seems
better than forcing them to always be the same.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Stephen Frost <sfrost@snowman.net> writes:
> > So I'm at a loss for why there is this insistence on a default role and
> > a superuser-explicit-granting based approach that goes beyond "is it
> > installed on the filesystem?" and "is it marked as trusted?".
>
> Okay, so it seems like we're down to just this one point of contention.

I agree that this seems to be the crux of the contention- though I need
to revisit what I wrote above because I didn't cover everything
relevant.  I'm suggesting that having:

- Extension installed on the filesystem
- Extension is marked as trusted
- Calling user has been made a DB owner (which, from a bare initdb,
  requires a superuser to take action to make happen)

is what would be needed to install a trusted extension.

> You feel that the superuser can control what is in the extension library
> directory and that that ought to be sufficient control.  I disagree
> with that, for two reasons:
>
> * ISTM that that's assuming that the DBA and the sysadmin are the same
> person (or at least hold identical views on this subject).  In many
> installations it'd only be root who has control over what's in that
> directory, and I don't think it's unreasonable for the DBA to wish
> to be able to exercise additional filtering.

I don't think we should start conflating roles by saying things like
"DBA" because it's not clear if that's "PG superuser" or "DB owner" or
"DB user".  In many, many, many installations the DBA is *not* the
superuser- in fact, pretty much every cloud-provider installation of PG
is that way.

Even so though, I don't agree with this particular rationale as, at
least largely in my experience, the sysadmin isn't going to go
installing things on their own- they're going to install what they've
been asked to, and it'll be a PG superuser or DB owner or DB user
doing the asking (and hopefully they'll consult with whomever is
appropriate before installing anything anyway).  The idea that
additional filtering on that is needed strikes me as highly unlikely.

Now, that said, I'm not strictly against the idea of allowing a
superuser, if they wish, to do additional filtering of what's allowed
(though I think there's a fair bit of complication in coming up with a
sensible way for them to do so, but that's an independent discussion).
I still don't think that the privilege to install a trusted extension
should be done through a default role though, or that it needs to be
independent from the DB owner role.

> * The point of a default role would be for the DBA to be able to
> control which database users can install extensions.  Even if the
> DBA has full authority over the extension library, that would not
> provide control over who can install, only over what is available
> for any of them to install.

In my approach, a superuser absolutely still has control over which
database users can install extensions, by virtue of being in control
over which users are DB owners, and further by being able to GRANT out
the right to install extensions, in specific databases, to specific
users.  If we want to have a mechanism where superusers can further
whitelist or blacklist extensions across the cluster, that's fine, but,
again, that's largely orthogonal to what I'm talking about.

Also, consider this- with the default role approach, is a user granted
that role allowed to create extensions in *every* database they can
connect to, or do they need some additional privilege, like CREATE
rights on the database, which is then under the purview of the database
owner?  What if the superuser wishes to allow a given role the ability
to install extensions only in a specific database?  That strikes me as
highly likely use-case ("you can install extensions in this other
database, but not in the 'postgres' database that I use for my
monitoring and other stuff") that isn't addressed at all with this
default role approach (looking at the patch, it seems to switch to the
superuser role to actually create objects, of course, so the caller
doesn't need create rights in the database), but is with mine- and
done so in a natural, intuitive, way that works just like the rest of
our privilege system.

I've always viewed the privilege system in PG to be a hierarchchy of
privileges-

superuser   - ultimate owner, able to do everything
DB owner    - controls create/use/modify for objects in the database,
              and altering of the database itself
schema owner- controls create/use/modify for objects in a schema, and
              altering of the schema itself
table owner - controls access/use/modify for the table, and altering of
              table itself

Now we're moving outside of that to start using default roles to control
who can create objects in a database, and that's what I don't agree
with.  If the superuser doesn't feel that a particular role should be
able to create extensions in a given database, there is a simple and
well understood solution- don't have that role own that database.  None
of what I've been talking about has ever taken away anything from the
superuser or made it such that the superuser wouldn't have any say in
who can install extensions.  I'll accept that if a superuser wished for
a given role to be able to run ALTER DATABASE but not install extensions
then that would present an issue in this model, but that feels like a
really tenuous argument and isn't one that anyone here has actually been
making, instead it seems to be a bunch of hand-wringing about how this
might be giving DB owners too much power, when they're already the ones
who control who is allowed to create objects in the database, or not.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> > ... and that backs up my position that we are setting up this
> > privilege at the wrong level by using a default role which a superuser must
> > grant independently from DB ownership.
>
> Don't see how this follows.  It's somewhat accidental I think that
> the existing behavior is tied to DB ownership.  That's just because
> at the time, that's the only sort of privilege we had that seemed
> intermediate between superuser and Joe User.  If we were designing
> the behavior today, with default roles already a done deal for
> handing out possibly-dangerous privileges, I think there's no
> question that we'd be setting up this privilege as a default role
> rather than tying it to DB ownership.  We don't make DB ownership
> a prerequisite to creating other sorts of functions, yet other
> functions can be just as dangerous in some cases as C functions.

I suppose I'll just have to say that I disagree.  I see a lot of value
in having a level between superuser and Joe User, and DB owner looks
pretty natural as exactly that, particularly for creating database-level
objects like extensions.

If anything, I tend to think we need more levels, not less- like a level
that's "cluster owner" or something along those lines, that's also
independent from "superuser" but would allow creating of cluster-level
objects like databases and roles (with the right to then GRANT the
ability to create those objects to other roles, if they wish).

I don't really see default roles as a better alternative to a privilege
hierarchy, but rather as a way for controlling access to things that
don't really fall into the hierarchy.  Maybe for cluster-level things
like what I hint at above they'd be better, but for database-level
objects, where you might decide you want to give a user access to create
something in database X but not in database Y?  Doesn't seem to fit very
well to me.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Stephen Frost <sfrost@snowman.net> writes:
> * Tom Lane (tgl@sss.pgh.pa.us) wrote:
>> Don't see how this follows.  It's somewhat accidental I think that
>> the existing behavior is tied to DB ownership.  That's just because
>> at the time, that's the only sort of privilege we had that seemed
>> intermediate between superuser and Joe User.  If we were designing
>> the behavior today, with default roles already a done deal for
>> handing out possibly-dangerous privileges, I think there's no
>> question that we'd be setting up this privilege as a default role
>> rather than tying it to DB ownership.  We don't make DB ownership
>> a prerequisite to creating other sorts of functions, yet other
>> functions can be just as dangerous in some cases as C functions.

> I suppose I'll just have to say that I disagree.  I see a lot of value
> in having a level between superuser and Joe User, and DB owner looks
> pretty natural as exactly that, particularly for creating database-level
> objects like extensions.

Well, the other direction we could go here, which I guess is what
you are arguing for, is to forget the new default role and just
say that marking an extension trusted allows it to be installed by
DB owners, full stop.  That's nice and simple and creates no
backwards-compatibility issues.  If we later decide that we want
a default role, or any other rules about who-can-install, we might
feel like this was a mistake --- but the backwards-compatibility issues
we'd incur by changing it later are exactly the same as what we'd have
today if we do something different from this.  The only difference
is that there'd be more extensions affected later (assuming we mark
more things trusted).

I'm willing to go with this solution if it'll end the argument.
Robert, Peter, what do you think?

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Fri, Jan 10, 2020 at 2:40 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Well, the other direction we could go here, which I guess is what
> you are arguing for, is to forget the new default role and just
> say that marking an extension trusted allows it to be installed by
> DB owners, full stop.  That's nice and simple and creates no
> backwards-compatibility issues.  If we later decide that we want
> a default role, or any other rules about who-can-install, we might
> feel like this was a mistake --- but the backwards-compatibility issues
> we'd incur by changing it later are exactly the same as what we'd have
> today if we do something different from this.  The only difference
> is that there'd be more extensions affected later (assuming we mark
> more things trusted).

I agree with your analysis, but I'm still inclined to feel that the
new pre-defined roll is a win.

Generally, decoupled permissions are better. Being able to grant
someone either A or B or both or neither is usually superior to having
to grant either both permissions or neither.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Robert Haas (robertmhaas@gmail.com) wrote:
> On Fri, Jan 10, 2020 at 2:40 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > Well, the other direction we could go here, which I guess is what
> > you are arguing for, is to forget the new default role and just
> > say that marking an extension trusted allows it to be installed by
> > DB owners, full stop.  That's nice and simple and creates no
> > backwards-compatibility issues.  If we later decide that we want
> > a default role, or any other rules about who-can-install, we might
> > feel like this was a mistake --- but the backwards-compatibility issues
> > we'd incur by changing it later are exactly the same as what we'd have
> > today if we do something different from this.  The only difference
> > is that there'd be more extensions affected later (assuming we mark
> > more things trusted).
>
> I agree with your analysis, but I'm still inclined to feel that the
> new pre-defined roll is a win.
>
> Generally, decoupled permissions are better. Being able to grant
> someone either A or B or both or neither is usually superior to having
> to grant either both permissions or neither.

Right- I like the idea of decoupled permissions too.

To be clear, I was advocating for a NEW DB-level privilege ('INSTALL' or
'CREATE EXTENSION' if we could make that work), so that we have it be
distinct from CREATE (which, today, really means 'CREATE SCHEMA').

I'd be willing to accept making this part of DB-level 'CREATE' rights if
there is a huge amount of push-back about burning a privilege bit for
it, but, as discussed up-thread, I don't think we should really be
stressing ourselves about that.

I do like the idea of having it be decoupled from explicit DB ownership,
so that a DB owner (or superuser) could say "I want this role to be able
to install extensions, but NOT run ALTER DATABASE", and optionally even
include ADMIN so that it could be further delegated (and also because
then it'd be just like the rest of our GRANT privilege system, and I
like that..).

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Stephen Frost <sfrost@snowman.net> writes:
> To be clear, I was advocating for a NEW DB-level privilege ('INSTALL' or
> 'CREATE EXTENSION' if we could make that work), so that we have it be
> distinct from CREATE (which, today, really means 'CREATE SCHEMA').

I still say this is wrong, or at least pointless, because it'd be a
right that any DB owner could grant to himself.  If we're to have any
meaningful access control on extension installation, the privilege
would have to be attached to some other object ... and there's no clear
candidate for what.  As someone noted awhile back, if we could somehow
attach ACLs to potentially-installable extensions, that might be an
interesting avenue to pursue.  That's well beyond what I'm willing
to pursue for v13, though.

In the meantime, though, this idea as stated doesn't do anything except
let a DB owner grant install privileges to someone else.  I'm not even
convinced that we want that, or that anyone needs it (I can recall zero
such requests related to PLs in the past).  And for sure it does not
belong in a minimal implementation of this feature.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Stephen Frost <sfrost@snowman.net> writes:
> > To be clear, I was advocating for a NEW DB-level privilege ('INSTALL' or
> > 'CREATE EXTENSION' if we could make that work), so that we have it be
> > distinct from CREATE (which, today, really means 'CREATE SCHEMA').
>
> I still say this is wrong, or at least pointless, because it'd be a
> right that any DB owner could grant to himself.

Yes, of course it is, that the DB owner would have this privilege was
something you agreed to in the prior email- I'd rather not just have a
"if (DBOwner())" check, I'd rather use our actual privilege system and
have this be a right that the DB owner has but can then GRANT out to
others if they wish to.

I'm certainly not suggesting that such a privilege wouldn't be
controlled by the DB owner.  Forcing it to only be allowed for the DB
owner and not be something that the DB owner can GRANT out isn't much
better than "if (superuser())"-style checks.

> If we're to have any
> meaningful access control on extension installation, the privilege
> would have to be attached to some other object ... and there's no clear
> candidate for what.

Extensions are installed at the DB level, not at any other level, and
therefore that's the appropriate place to attach them, which is exactly
what I'm suggesting we do here.

> As someone noted awhile back, if we could somehow
> attach ACLs to potentially-installable extensions, that might be an
> interesting avenue to pursue.  That's well beyond what I'm willing
> to pursue for v13, though.

Sure, having some catalog of installable extensions where someone (in my
thinking, the DB owner) could GRANT out access to install certain
extensions to others might be interesting, but it's not what I'm
suggesting here.

> In the meantime, though, this idea as stated doesn't do anything except
> let a DB owner grant install privileges to someone else.  I'm not even
> convinced that we want that, or that anyone needs it (I can recall zero
> such requests related to PLs in the past).  And for sure it does not
> belong in a minimal implementation of this feature.

Yes, that's what this approach would do.  I suppose an alternative would
be to lump it in with "CREATE" rights on the DB, but I've advocated and
will continue to advocate for splitting up of such broad rights.
DB-level CREATE rights currently cover both schemas and publications,
for example, even though the two have rather little to do with each
other.

If the only agreeable option is a if (DBOwner())-type check, or lumping
the privilege to CREATE (trusted) EXTENSION in with other DB-level
CREATE rights, then I'll go along with one of those.  I'll be happy
enough with that, since it avoids having an additional default role that
has to be GRANT'd by a superuser.  Ideally, down the road, we'll split
out the CREATE privilege (both at DB and at schema level) to be more
fine grained, but that can certainly be done later.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Stephen Frost <sfrost@snowman.net> writes:
> * Tom Lane (tgl@sss.pgh.pa.us) wrote:
>> In the meantime, though, this idea as stated doesn't do anything except
>> let a DB owner grant install privileges to someone else.  I'm not even
>> convinced that we want that, or that anyone needs it (I can recall zero
>> such requests related to PLs in the past).  And for sure it does not
>> belong in a minimal implementation of this feature.

> Yes, that's what this approach would do.  I suppose an alternative would
> be to lump it in with "CREATE" rights on the DB, but I've advocated and
> will continue to advocate for splitting up of such broad rights.
> DB-level CREATE rights currently cover both schemas and publications,
> for example, even though the two have rather little to do with each
> other.

The patch as I'm proposing it has nothing to do with "CREATE" rights.
You're attacking something different from what I actually want to do.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Stephen Frost <sfrost@snowman.net> writes:
> > * Tom Lane (tgl@sss.pgh.pa.us) wrote:
> >> In the meantime, though, this idea as stated doesn't do anything except
> >> let a DB owner grant install privileges to someone else.  I'm not even
> >> convinced that we want that, or that anyone needs it (I can recall zero
> >> such requests related to PLs in the past).  And for sure it does not
> >> belong in a minimal implementation of this feature.
>
> > Yes, that's what this approach would do.  I suppose an alternative would
> > be to lump it in with "CREATE" rights on the DB, but I've advocated and
> > will continue to advocate for splitting up of such broad rights.
> > DB-level CREATE rights currently cover both schemas and publications,
> > for example, even though the two have rather little to do with each
> > other.
>
> The patch as I'm proposing it has nothing to do with "CREATE" rights.
> You're attacking something different from what I actually want to do.

Yes, as an aside, I'm argueing that we should split up the general
CREATE privileges, but I also said that's not required for this.

You're asking "what's the best way to add this privilege to PG?".  I'm
saying that it should be done through the privilege system, similar to
publications.  I'd prefer it not be lumped into CREATE, but that at
least makes sense to me- adding a default role for this doesn't.  I
suppose making it akin to ALTER DATABASE and having it be limited to the
DB owner is also alright (as I said in my last email) but it means that
someone has to be given DB ownership rights in order to install
extensions.  I don't really see CREATE EXTENSION as being like ALTER
DATABASE from a privilege perspective, but having it be DB owner still
makes more sense than a default role for this.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Stephen Frost <sfrost@snowman.net> writes:
> * Tom Lane (tgl@sss.pgh.pa.us) wrote:
>> The patch as I'm proposing it has nothing to do with "CREATE" rights.
>> You're attacking something different from what I actually want to do.

> Yes, as an aside, I'm argueing that we should split up the general
> CREATE privileges, but I also said that's not required for this.

So how do we move this forward?  I really don't want this patch to be
blocked by what's fundamentally a side point about permissions.

The minimum committable patch seems like it would just grant the
"can install trusted extensions" ability to DB owners, full stop.
This is small, it's exactly the same as our historical behavior for
trusted PLs, and it's upward compatible with either of two possible
future extensions:

* adding a predefined role (which'd let superusers give out the install
privilege, in addition to DB owners having it)

* converting DB owners' hard-wired privilege to a grantable privilege
(which'd let DB owners give out the install privilege, if the privilege
is attached to the DBs themselves; but maybe there's some other way?)

Given the lack of consensus about either of those being what we want,
it doesn't seem like we're going to come to an agreement in a
reasonable timeframe on a patch that includes either.  So I'd like
to get this done and move on to the next problem (ie, what is it
we're actually going to do about the python 2/3 mess).

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Tue, Jan 21, 2020 at 12:40 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Given the lack of consensus about either of those being what we want,
> it doesn't seem like we're going to come to an agreement in a
> reasonable timeframe on a patch that includes either.  So I'd like
> to get this done and move on to the next problem (ie, what is it
> we're actually going to do about the python 2/3 mess).

I'm fine with that.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Stephen Frost <sfrost@snowman.net> writes:
> > * Tom Lane (tgl@sss.pgh.pa.us) wrote:
> >> The patch as I'm proposing it has nothing to do with "CREATE" rights.
> >> You're attacking something different from what I actually want to do.
>
> > Yes, as an aside, I'm argueing that we should split up the general
> > CREATE privileges, but I also said that's not required for this.
>
> So how do we move this forward?  I really don't want this patch to be
> blocked by what's fundamentally a side point about permissions.
>
> The minimum committable patch seems like it would just grant the
> "can install trusted extensions" ability to DB owners, full stop.

If you're alright with making it something a DB owner can do, what is
the issue with making it part of the CREATE right on the database?  That
doesn't require any additional permission bits, in a default setup
doesn't change who is able to create extensions (in either your proposal
or mine, it's the DB owner), and in either proposal means that people
who couldn't create extensions with PG12 will be able to create them in
PG13.

We've added other things to the DB-level CREATE rights rather recently
too, so it's not like we've historically avoided that.

The argument you presented previously against that idea was because it
would mean the DB owner would still be able to exercise that right,
which is what you're now proposing anyway and which I was always
advocating for and wasn't trying to say wouldn't be the case with that
approach.

So I'm at a loss for what the actual argument is against making it part
of DB-level CREATE.

> This is small, it's exactly the same as our historical behavior for
> trusted PLs, and it's upward compatible with either of two possible
> future extensions:
>
> * adding a predefined role (which'd let superusers give out the install
> privilege, in addition to DB owners having it)

Uh, just to be clear, even with your approach, a DB owner could 'GRANT'
the necessary right for another user to install extensions by simply
GRANT'ing their own role to that user.  Obviously, that conveys other
privileges with it, but we have that problem at any level as long as we
constrain ourselves to a single set of 32 bits for representing
privileges.  I see it as being manifestly better to lump it in with the
DB-level CREATE privilege though.

> * converting DB owners' hard-wired privilege to a grantable privilege
> (which'd let DB owners give out the install privilege, if the privilege
> is attached to the DBs themselves; but maybe there's some other way?)

In either of these proposals, we could split up the bounded-together
privileges down the road, and, sure, there might be more than one way to
do that, but I really don't want to go down a road where every privilege
ends up being split up into a seperate default-role (or predefined role
or whatever we want to call those things today).

> Given the lack of consensus about either of those being what we want,
> it doesn't seem like we're going to come to an agreement in a
> reasonable timeframe on a patch that includes either.  So I'd like
> to get this done and move on to the next problem (ie, what is it
> we're actually going to do about the python 2/3 mess).

I get that you want to push forward with making this part of the DB
owner, and I said up-thread that I'd be able to live with that, but I
still don't understand what the argument is against making it part of
CREATE instead.

Thanks,

Stephen

Attachment

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Tue, Jan 28, 2020 at 3:29 PM Stephen Frost <sfrost@snowman.net> wrote:
> I get that you want to push forward with making this part of the DB
> owner, and I said up-thread that I'd be able to live with that, but I
> still don't understand what the argument is against making it part of
> CREATE instead.

It's a change from the status quo. If we're going to how it works, we
should try to agree on how it ought to work. Tom's proposal dodges
that by leaving things exactly as they are, deferring any actual
modifications to who can do what to a future patch.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Stephen Frost <sfrost@snowman.net> writes:
> * Tom Lane (tgl@sss.pgh.pa.us) wrote:
>> The minimum committable patch seems like it would just grant the
>> "can install trusted extensions" ability to DB owners, full stop.

> If you're alright with making it something a DB owner can do, what is
> the issue with making it part of the CREATE right on the database?

Um, well, people were complaining that it should be a distinct privilege,
which I for one wasn't sold on.

I continue to think that allowing DB owners to decide this is, if not
fundamentally the wrong thing, at least not a feature that anybody has
asked for in the past.  The feature *I* want in this area is for the
superuser to be able to decide who's got install privilege.  Making
it a DB-level privilege doesn't serve that goal, more the opposite.

Still, if we can compromise by making this part of DB "CREATE" privilege
for the time being, I'm willing to take that compromise.  It's certainly
better than failing to get rid of pg_pltemplate.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Robert Haas
Date:
On Tue, Jan 28, 2020 at 3:52 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I continue to think that allowing DB owners to decide this is, if not
> fundamentally the wrong thing, at least not a feature that anybody has
> asked for in the past.  The feature *I* want in this area is for the
> superuser to be able to decide who's got install privilege.  Making
> it a DB-level privilege doesn't serve that goal, more the opposite.

I agree.

> Still, if we can compromise by making this part of DB "CREATE" privilege
> for the time being, I'm willing to take that compromise.  It's certainly
> better than failing to get rid of pg_pltemplate.

Doesn't that have exactly the issue you describe above?

bob=> grant create on database bob to fred;
GRANT

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> On Tue, Jan 28, 2020 at 3:52 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I continue to think that allowing DB owners to decide this is, if not
>> fundamentally the wrong thing, at least not a feature that anybody has
>> asked for in the past.  The feature *I* want in this area is for the
>> superuser to be able to decide who's got install privilege.  Making
>> it a DB-level privilege doesn't serve that goal, more the opposite.

> I agree.

>> Still, if we can compromise by making this part of DB "CREATE" privilege
>> for the time being, I'm willing to take that compromise.  It's certainly
>> better than failing to get rid of pg_pltemplate.

> Doesn't that have exactly the issue you describe above?
> bob=> grant create on database bob to fred;
> GRANT

Either of them do, in that a DB owner can always grant his whole role;
"grant bob to fred" will give fred install privileges (in bob's DBs)
regardless of which of these choices we adopt.  And that was true before
(with respect to trusted PLs), too.  Attaching the ability to the CREATE
bit would at least allow DB owners to be a bit more selective about how
they give it out.

The reason I'm happier about doing this with CREATE than inventing
a separate INSTALL bit is that once we do the latter, we're more or
less bound to keep supporting that ability forever.  If we extend
the definition of CREATE in v13, and then narrow it again in some
future release, that seems less likely to cause problems than taking
away a named privilege bit would do.

On the other hand, there's the point that lots of people have probably
given out schema-CREATE privilege to users whom they wouldn't necessarily
wish to trust with INSTALL privilege.  Schema-CREATE is a pretty harmless
privilege, INSTALL much less so.

I do like your point about how maybe we shouldn't change the status quo
without more consensus than we've got ... but in the end I just want
to get this done and move on.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Stephen Frost
Date:
Greetings,

On Tue, Jan 28, 2020 at 16:17 Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
> On Tue, Jan 28, 2020 at 3:52 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I continue to think that allowing DB owners to decide this is, if not
>> fundamentally the wrong thing, at least not a feature that anybody has
>> asked for in the past.  The feature *I* want in this area is for the
>> superuser to be able to decide who's got install privilege.  Making
>> it a DB-level privilege doesn't serve that goal, more the opposite.

> I agree.

>> Still, if we can compromise by making this part of DB "CREATE" privilege
>> for the time being, I'm willing to take that compromise.  It's certainly
>> better than failing to get rid of pg_pltemplate.

> Doesn't that have exactly the issue you describe above?
> bob=> grant create on database bob to fred;
> GRANT

Either of them do, in that a DB owner can always grant his whole role;
"grant bob to fred" will give fred install privileges (in bob's DBs)
regardless of which of these choices we adopt.  And that was true before
(with respect to trusted PLs), too.  Attaching the ability to the CREATE
bit would at least allow DB owners to be a bit more selective about how
they give it out.

Right.

The reason I'm happier about doing this with CREATE than inventing
a separate INSTALL bit is that once we do the latter, we're more or
less bound to keep supporting that ability forever.  If we extend
the definition of CREATE in v13, and then narrow it again in some
future release, that seems less likely to cause problems than taking
away a named privilege bit would do.

I would like to segregate these privileges more than just “install” vs “other stuff” in the future anyway, so mixing it with the existing CREATE isn’t that big of a deal in my view. 

On the other hand, there's the point that lots of people have probably
given out schema-CREATE privilege to users whom they wouldn't necessarily
wish to trust with INSTALL privilege.  Schema-CREATE is a pretty harmless
privilege, INSTALL much less so.

CREATE doesn’t just control the ability to create schemas these days- it was extended to cover publications also not that long ago.  We never said we wouldn’t extend CREATE to cover more objects and we’ve already extended it recently, without anyone being up in arms about it that I can recall, so this doesn’t feel like a huge issue or concern to me.  Note that, again, these are trusted extensions which means, in their regular install, they shouldn’t be able to “break outside the box” any more than a plpgsql function is able to.

Thanks,

Stephen

Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
Stephen Frost <sfrost@snowman.net> writes:
> On Tue, Jan 28, 2020 at 16:17 Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> On the other hand, there's the point that lots of people have probably
>> given out schema-CREATE privilege to users whom they wouldn't necessarily
>> wish to trust with INSTALL privilege.  Schema-CREATE is a pretty harmless
>> privilege, INSTALL much less so.

> CREATE doesn’t just control the ability to create schemas these days- it
> was extended to cover publications also not that long ago.

Oh really ... hm, that does make it a much bigger deal than I was
thinking.  Given that, I don't think there's any huge objection to
attaching this to CREATE, at least till we get around to a more
significant redesign.

            regards, tom lane



Re: Removing pg_pltemplate and creating "trustable" extensions

From
Tom Lane
Date:
I wrote:
> Stephen Frost <sfrost@snowman.net> writes:
>> On Tue, Jan 28, 2020 at 16:17 Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> On the other hand, there's the point that lots of people have probably
>>> given out schema-CREATE privilege to users whom they wouldn't necessarily
>>> wish to trust with INSTALL privilege.  Schema-CREATE is a pretty harmless
>>> privilege, INSTALL much less so.

>> CREATE doesn't just control the ability to create schemas these days- it
>> was extended to cover publications also not that long ago.

> Oh really ... hm, that does make it a much bigger deal than I was
> thinking.  Given that, I don't think there's any huge objection to
> attaching this to CREATE, at least till we get around to a more
> significant redesign.

Here's a v5 that drops the new predefined role and allows
trusted-extension installation when you have CREATE on the current
database.  There's no other changes except a bit of documentation
wordsmithing.

Barring further complaints, I'm going to push this fairly soon.

            regards, tom lane

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 85ac79f..a10b665 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8519,7 +8519,15 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
      <row>
       <entry><structfield>superuser</structfield></entry>
       <entry><type>bool</type></entry>
-      <entry>True if only superusers are allowed to install this extension</entry>
+      <entry>True if only superusers are allowed to install this extension
+       (but see <structfield>trusted</structfield>)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>trusted</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry>True if the extension can be installed by non-superusers
+       with appropriate privileges</entry>
      </row>

      <row>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 3546e39..8d3a0d1 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1742,6 +1742,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
      <listitem>
       <para>
        For databases, allows new schemas and publications to be created within
+       the database, and allows trusted extensions to be installed within
        the database.
       </para>
       <para>
@@ -1753,8 +1754,11 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <para>
        For tablespaces, allows tables, indexes, and temporary files to be
        created within the tablespace, and allows databases to be created that
-       have the tablespace as their default tablespace.  (Note that revoking
-       this privilege will not alter the placement of existing objects.)
+       have the tablespace as their default tablespace.
+      </para>
+      <para>
+       Note that revoking this privilege will not alter the existence or
+       location of existing objects.
       </para>
      </listitem>
     </varlistentry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f2..ffe068b 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -576,6 +576,31 @@
         version.  If it is set to <literal>false</literal>, just the privileges
         required to execute the commands in the installation or update script
         are required.
+        This should normally be set to <literal>true</literal> if any of the
+        script commands require superuser privileges.  (Such commands would
+        fail anyway, but it's more user-friendly to give the error up front.)
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>trusted</varname> (<type>boolean</type>)</term>
+      <listitem>
+       <para>
+        This parameter, if set to <literal>true</literal> (which is not the
+        default), allows some non-superusers to install an extension that
+        has <varname>superuser</varname> set to <literal>true</literal>.
+        Specifically, installation will be permitted for anyone who has
+        <literal>CREATE</literal> privilege on the current database.
+        When the user executing <command>CREATE EXTENSION</command> is not
+        a superuser but is allowed to install by virtue of this parameter,
+        then the installation or update script is run as the bootstrap
+        superuser, not as the calling user.
+        This parameter is irrelevant if <varname>superuser</varname> is
+        <literal>false</literal>.
+        Generally, this should not be set true for extensions that could
+        allow access to otherwise-superuser-only abilities, such as
+        filesystem access.
        </para>
       </listitem>
      </varlistentry>
@@ -642,6 +667,18 @@
     </para>

     <para>
+     If the extension script contains the
+     string <literal>@extowner@</literal>, that string is replaced with the
+     (suitably quoted) name of the user calling <command>CREATE
+     EXTENSION</command> or <command>ALTER EXTENSION</command>.  Typically
+     this feature is used by extensions that are marked trusted to assign
+     ownership of selected objects to the calling user rather than the
+     bootstrap superuser.  (One should be careful about doing so, however.
+     For example, assigning ownership of a C-language function to a
+     non-superuser would create a privilege escalation path for that user.)
+    </para>
+
+    <para>
      While the script files can contain any characters allowed by the specified
      encoding, control files should contain only plain ASCII, because there
      is no way for <productname>PostgreSQL</productname> to know what encoding a
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index 36837f9..d76ac3e 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -47,14 +47,25 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
   </para>

   <para>
-   Loading an extension requires the same privileges that would be
-   required to create its component objects.  For most extensions this
-   means superuser or database owner privileges are needed.
    The user who runs <command>CREATE EXTENSION</command> becomes the
    owner of the extension for purposes of later privilege checks, as well
    as the owner of any objects created by the extension's script.
   </para>

+  <para>
+   Loading an extension ordinarily requires the same privileges that would
+   be required to create its component objects.  For many extensions this
+   means superuser privileges are needed.
+   However, if the extension is marked <firstterm>trusted</firstterm> in
+   its control file, then it can be installed by any user who has
+   <literal>CREATE</literal> privilege on the current database.
+   In this case the extension object itself will be owned by the calling
+   user, but the contained objects will be owned by the bootstrap superuser
+   (unless the extension's script explicitly assigns them to the calling
+   user).  This configuration gives the calling user the right to drop the
+   extension, but not to modify individual objects within it.
+  </para>
+
  </refsect1>

  <refsect1>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c9e75f4..c9e6060 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -317,7 +317,8 @@ CREATE VIEW pg_available_extensions AS

 CREATE VIEW pg_available_extension_versions AS
     SELECT E.name, E.version, (X.extname IS NOT NULL) AS installed,
-           E.superuser, E.relocatable, E.schema, E.requires, E.comment
+           E.superuser, E.trusted, E.relocatable,
+           E.schema, E.requires, E.comment
       FROM pg_available_extension_versions() AS E
            LEFT JOIN pg_extension AS X
              ON E.name = X.extname AND E.version = X.extversion;
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 01de398..ddd46f4 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -40,6 +40,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
@@ -84,6 +85,7 @@ typedef struct ExtensionControlFile
     char       *schema;            /* target schema (allowed if !relocatable) */
     bool        relocatable;    /* is ALTER EXTENSION SET SCHEMA supported? */
     bool        superuser;        /* must be superuser to install? */
+    bool        trusted;        /* allow becoming superuser on the fly? */
     int            encoding;        /* encoding of the script file, or -1 */
     List       *requires;        /* names of prerequisite extensions */
 } ExtensionControlFile;
@@ -558,6 +560,14 @@ parse_extension_control_file(ExtensionControlFile *control,
                          errmsg("parameter \"%s\" requires a Boolean value",
                                 item->name)));
         }
+        else if (strcmp(item->name, "trusted") == 0)
+        {
+            if (!parse_bool(item->value, &control->trusted))
+                ereport(ERROR,
+                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                         errmsg("parameter \"%s\" requires a Boolean value",
+                                item->name)));
+        }
         else if (strcmp(item->name, "encoding") == 0)
         {
             control->encoding = pg_valid_server_encoding(item->value);
@@ -614,6 +624,7 @@ read_extension_control_file(const char *extname)
     control->name = pstrdup(extname);
     control->relocatable = false;
     control->superuser = true;
+    control->trusted = false;
     control->encoding = -1;

     /*
@@ -795,6 +806,27 @@ execute_sql_string(const char *sql)
 }

 /*
+ * Policy function: is the given extension trusted for installation by a
+ * non-superuser?
+ *
+ * (Update the errhint logic below if you change this.)
+ */
+static bool
+extension_is_trusted(ExtensionControlFile *control)
+{
+    AclResult    aclresult;
+
+    /* Never trust unless extension's control file says it's okay */
+    if (!control->trusted)
+        return false;
+    /* Allow if user has CREATE privilege on current database */
+    aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
+    if (aclresult == ACLCHECK_OK)
+        return true;
+    return false;
+}
+
+/*
  * Execute the appropriate script file for installing or updating the extension
  *
  * If from_version isn't NULL, it's an update
@@ -806,35 +838,56 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                          List *requiredSchemas,
                          const char *schemaName, Oid schemaOid)
 {
+    bool        switch_to_superuser = false;
     char       *filename;
+    Oid            save_userid = 0;
+    int            save_sec_context = 0;
     int            save_nestlevel;
     StringInfoData pathbuf;
     ListCell   *lc;

     /*
-     * Enforce superuser-ness if appropriate.  We postpone this check until
-     * here so that the flag is correctly associated with the right script(s)
-     * if it's set in secondary control files.
+     * Enforce superuser-ness if appropriate.  We postpone these checks until
+     * here so that the control flags are correctly associated with the right
+     * script(s) if they happen to be set in secondary control files.
      */
     if (control->superuser && !superuser())
     {
-        if (from_version == NULL)
+        if (extension_is_trusted(control))
+            switch_to_superuser = true;
+        else if (from_version == NULL)
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("permission denied to create extension \"%s\"",
                             control->name),
-                     errhint("Must be superuser to create this extension.")));
+                     control->trusted
+                     ? errhint("Must have CREATE privilege on current database to create this extension.")
+                     : errhint("Must be superuser to create this extension.")));
         else
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("permission denied to update extension \"%s\"",
                             control->name),
-                     errhint("Must be superuser to update this extension.")));
+                     control->trusted
+                     ? errhint("Must have CREATE privilege on current database to update this extension.")
+                     : errhint("Must be superuser to update this extension.")));
     }

     filename = get_extension_script_filename(control, from_version, version);

     /*
+     * If installing a trusted extension on behalf of a non-superuser, become
+     * the bootstrap superuser.  (This switch will be cleaned up automatically
+     * if the transaction aborts, as will the GUC changes below.)
+     */
+    if (switch_to_superuser)
+    {
+        GetUserIdAndSecContext(&save_userid, &save_sec_context);
+        SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
+                               save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+    }
+
+    /*
      * 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.
@@ -907,6 +960,22 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                                         CStringGetTextDatum("ng"));

         /*
+         * If the script uses @extowner@, substitute the calling username.
+         */
+        if (strstr(c_sql, "@extowner@"))
+        {
+            Oid            uid = switch_to_superuser ? save_userid : GetUserId();
+            const char *userName = GetUserNameFromId(uid, false);
+            const char *qUserName = quote_identifier(userName);
+
+            t_sql = DirectFunctionCall3Coll(replace_text,
+                                            C_COLLATION_OID,
+                                            t_sql,
+                                            CStringGetTextDatum("@extowner@"),
+                                            CStringGetTextDatum(qUserName));
+        }
+
+        /*
          * If it's not relocatable, substitute the target schema name for
          * occurrences of @extschema@.
          *
@@ -953,6 +1022,12 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
      * Restore the GUC variables we set above.
      */
     AtEOXact_GUC(true, save_nestlevel);
+
+    /*
+     * Restore authentication state if needed.
+     */
+    if (switch_to_superuser)
+        SetUserIdAndSecContext(save_userid, save_sec_context);
 }

 /*
@@ -2111,8 +2186,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
     {
         ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
         ExtensionControlFile *control;
-        Datum        values[7];
-        bool        nulls[7];
+        Datum        values[8];
+        bool        nulls[8];
         ListCell   *lc2;

         if (!evi->installable)
@@ -2133,24 +2208,26 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
         values[1] = CStringGetTextDatum(evi->name);
         /* superuser */
         values[2] = BoolGetDatum(control->superuser);
+        /* trusted */
+        values[3] = BoolGetDatum(control->trusted);
         /* relocatable */
-        values[3] = BoolGetDatum(control->relocatable);
+        values[4] = BoolGetDatum(control->relocatable);
         /* schema */
         if (control->schema == NULL)
-            nulls[4] = true;
+            nulls[5] = true;
         else
-            values[4] = DirectFunctionCall1(namein,
+            values[5] = DirectFunctionCall1(namein,
                                             CStringGetDatum(control->schema));
         /* requires */
         if (control->requires == NIL)
-            nulls[5] = true;
+            nulls[6] = true;
         else
-            values[5] = convert_requires_to_datum(control->requires);
+            values[6] = convert_requires_to_datum(control->requires);
         /* comment */
         if (control->comment == NULL)
-            nulls[6] = true;
+            nulls[7] = true;
         else
-            values[6] = CStringGetTextDatum(control->comment);
+            values[7] = CStringGetTextDatum(control->comment);

         tuplestore_putvalues(tupstore, tupdesc, values, nulls);

@@ -2178,16 +2255,18 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
                 values[1] = CStringGetTextDatum(evi2->name);
                 /* superuser */
                 values[2] = BoolGetDatum(control->superuser);
+                /* trusted */
+                values[3] = BoolGetDatum(control->trusted);
                 /* relocatable */
-                values[3] = BoolGetDatum(control->relocatable);
+                values[4] = BoolGetDatum(control->relocatable);
                 /* schema stays the same */
                 /* requires */
                 if (control->requires == NIL)
-                    nulls[5] = true;
+                    nulls[6] = true;
                 else
                 {
-                    values[5] = convert_requires_to_datum(control->requires);
-                    nulls[5] = false;
+                    values[6] = convert_requires_to_datum(control->requires);
+                    nulls[6] = false;
                 }
                 /* comment stays the same */

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bef50c7..2228256 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9496,9 +9496,9 @@
   proname => 'pg_available_extension_versions', procost => '10',
   prorows => '100', proretset => 't', provolatile => 's',
   prorettype => 'record', proargtypes => '',
-  proallargtypes => '{name,text,bool,bool,name,_name,text}',
-  proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,version,superuser,relocatable,schema,requires,comment}',
+  proallargtypes => '{name,text,bool,bool,bool,name,_name,text}',
+  proargmodes => '{o,o,o,o,o,o,o,o}',
+  proargnames => '{name,version,superuser,trusted,relocatable,schema,requires,comment}',
   prosrc => 'pg_available_extension_versions' },
 { oid => '3084', descr => 'list an extension\'s version update paths',
   proname => 'pg_extension_update_paths', procost => '10', prorows => '100',
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 70e1e2f..2ab2115 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1311,11 +1311,12 @@ pg_available_extension_versions| SELECT e.name,
     e.version,
     (x.extname IS NOT NULL) AS installed,
     e.superuser,
+    e.trusted,
     e.relocatable,
     e.schema,
     e.requires,
     e.comment
-   FROM (pg_available_extension_versions() e(name, version, superuser, relocatable, schema, requires, comment)
+   FROM (pg_available_extension_versions() e(name, version, superuser, trusted, relocatable, schema, requires,
comment)
      LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion))));
 pg_available_extensions| SELECT e.name,
     e.default_version,
diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile
index 9b1c514..e4d0a0b 100644
--- a/src/pl/plperl/GNUmakefile
+++ b/src/pl/plperl/GNUmakefile
@@ -55,8 +55,10 @@ endif # win32

 SHLIB_LINK = $(perl_embed_ldflags)

-REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-extension=plperl  --load-extension=plperlu
-REGRESS = plperl plperl_lc plperl_trigger plperl_shared plperl_elog plperl_util plperl_init plperlu plperl_array
plperl_callplperl_transaction 
+REGRESS_OPTS = --dbname=$(PL_TESTDB)
+REGRESS = plperl_setup plperl plperl_lc plperl_trigger plperl_shared \
+    plperl_elog plperl_util plperl_init plperlu plperl_array \
+    plperl_call plperl_transaction
 # if Perl can support two interpreters in one backend,
 # test plperl-and-plperlu cases
 ifneq ($(PERL),)
diff --git a/src/pl/plperl/expected/plperl_setup.out b/src/pl/plperl/expected/plperl_setup.out
new file mode 100644
index 0000000..faeb645
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_setup.out
@@ -0,0 +1,66 @@
+--
+-- Install the plperl and plperlu extensions
+--
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- suitable permissions have been granted.
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;  -- fail
+ERROR:  permission denied to create extension "plperl"
+HINT:  Must have CREATE privilege on current database to create this extension.
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+RESET ROLE;
+DO $$
+begin
+  execute format('grant create on database %I to regress_user1',
+                 current_database());
+end;
+$$;
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+ foo1
+------
+    1
+(1 row)
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+ERROR:  permission denied for language plperl
+SET ROLE regress_user1;
+grant usage on language plperl to regress_user2;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+ foo2
+------
+    2
+(1 row)
+
+SET ROLE regress_user1;
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+ERROR:  cannot drop language plperl because extension plperl requires it
+HINT:  You can drop extension plperl instead.
+DROP EXTENSION plperl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to function foo1()
+drop cascades to function foo2()
+-- Clean up
+RESET ROLE;
+DROP OWNED BY regress_user1;
+DROP USER regress_user1;
+DROP USER regress_user2;
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plperl/plperl--1.0.sql b/src/pl/plperl/plperl--1.0.sql
index f716ba1..5ff31e7 100644
--- a/src/pl/plperl/plperl--1.0.sql
+++ b/src/pl/plperl/plperl--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plperl/plperl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperl;
+CREATE FUNCTION plperl_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperl_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plperl
+  HANDLER plperl_call_handler
+  INLINE plperl_inline_handler
+  VALIDATOR plperl_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plperl OWNER TO @extowner@;

 COMMENT ON LANGUAGE plperl IS 'PL/Perl procedural language';
diff --git a/src/pl/plperl/plperl.control b/src/pl/plperl/plperl.control
index 6faace1..3a2230a 100644
--- a/src/pl/plperl/plperl.control
+++ b/src/pl/plperl/plperl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plperl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/plperl/plperlu--1.0.sql b/src/pl/plperl/plperlu--1.0.sql
index 7efb4fb..10d7594 100644
--- a/src/pl/plperl/plperlu--1.0.sql
+++ b/src/pl/plperl/plperlu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plperl/plperlu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperlu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperlu;
+CREATE FUNCTION plperlu_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperlu_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plperlu
+  HANDLER plperlu_call_handler
+  INLINE plperlu_inline_handler
+  VALIDATOR plperlu_validator;

 COMMENT ON LANGUAGE plperlu IS 'PL/PerlU untrusted procedural language';
diff --git a/src/pl/plperl/sql/plperl_setup.sql b/src/pl/plperl/sql/plperl_setup.sql
new file mode 100644
index 0000000..ae48fea
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_setup.sql
@@ -0,0 +1,64 @@
+--
+-- Install the plperl and plperlu extensions
+--
+
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- suitable permissions have been granted.
+
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;  -- fail
+CREATE EXTENSION plperlu;  -- fail
+
+RESET ROLE;
+
+DO $$
+begin
+  execute format('grant create on database %I to regress_user1',
+                 current_database());
+end;
+$$;
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+
+SET ROLE regress_user1;
+
+grant usage on language plperl to regress_user2;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+
+SET ROLE regress_user1;
+
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+DROP EXTENSION plperl CASCADE;
+
+-- Clean up
+RESET ROLE;
+DROP OWNED BY regress_user1;
+DROP USER regress_user1;
+DROP USER regress_user2;
+
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plpgsql/src/plpgsql--1.0.sql b/src/pl/plpgsql/src/plpgsql--1.0.sql
index ab6fa84..6e5b990 100644
--- a/src/pl/plpgsql/src/plpgsql--1.0.sql
+++ b/src/pl/plpgsql/src/plpgsql--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plpgsql/src/plpgsql--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpgsql_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpgsql;
+CREATE FUNCTION plpgsql_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpgsql_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plpgsql
+  HANDLER plpgsql_call_handler
+  INLINE plpgsql_inline_handler
+  VALIDATOR plpgsql_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plpgsql OWNER TO @extowner@;

 COMMENT ON LANGUAGE plpgsql IS 'PL/pgSQL procedural language';
diff --git a/src/pl/plpgsql/src/plpgsql.control b/src/pl/plpgsql/src/plpgsql.control
index b320227..42e764b 100644
--- a/src/pl/plpgsql/src/plpgsql.control
+++ b/src/pl/plpgsql/src/plpgsql.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plpgsql'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/plpython/plpython2u--1.0.sql b/src/pl/plpython/plpython2u--1.0.sql
index 661cc66..69f7477 100644
--- a/src/pl/plpython/plpython2u--1.0.sql
+++ b/src/pl/plpython/plpython2u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython2u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython2_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython2u;
+CREATE FUNCTION plpython2_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython2_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython2u
+  HANDLER plpython2_call_handler
+  INLINE plpython2_inline_handler
+  VALIDATOR plpython2_validator;

 COMMENT ON LANGUAGE plpython2u IS 'PL/Python2U untrusted procedural language';
diff --git a/src/pl/plpython/plpython3u--1.0.sql b/src/pl/plpython/plpython3u--1.0.sql
index c0d6ea8..ba2e6ac 100644
--- a/src/pl/plpython/plpython3u--1.0.sql
+++ b/src/pl/plpython/plpython3u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython3u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython3_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython3u;
+CREATE FUNCTION plpython3_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython3_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython3u
+  HANDLER plpython3_call_handler
+  INLINE plpython3_inline_handler
+  VALIDATOR plpython3_validator;

 COMMENT ON LANGUAGE plpython3u IS 'PL/Python3U untrusted procedural language';
diff --git a/src/pl/plpython/plpythonu--1.0.sql b/src/pl/plpython/plpythonu--1.0.sql
index 4a3e64a..4c6f7c3 100644
--- a/src/pl/plpython/plpythonu--1.0.sql
+++ b/src/pl/plpython/plpythonu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpythonu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpythonu;
+CREATE FUNCTION plpython_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpythonu
+  HANDLER plpython_call_handler
+  INLINE plpython_inline_handler
+  VALIDATOR plpython_validator;

 COMMENT ON LANGUAGE plpythonu IS 'PL/PythonU untrusted procedural language';
diff --git a/src/pl/tcl/pltcl--1.0.sql b/src/pl/tcl/pltcl--1.0.sql
index 34a68c8..2ed2b92 100644
--- a/src/pl/tcl/pltcl--1.0.sql
+++ b/src/pl/tcl/pltcl--1.0.sql
@@ -1,11 +1,12 @@
 /* src/pl/tcl/pltcl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltcl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltcl;
+CREATE TRUSTED LANGUAGE pltcl
+  HANDLER pltcl_call_handler;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE pltcl OWNER TO @extowner@;

 COMMENT ON LANGUAGE pltcl IS 'PL/Tcl procedural language';
diff --git a/src/pl/tcl/pltcl.control b/src/pl/tcl/pltcl.control
index b9dc1b8..1568c17 100644
--- a/src/pl/tcl/pltcl.control
+++ b/src/pl/tcl/pltcl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/pltcl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trusted = true
diff --git a/src/pl/tcl/pltclu--1.0.sql b/src/pl/tcl/pltclu--1.0.sql
index e05b470..fca869f 100644
--- a/src/pl/tcl/pltclu--1.0.sql
+++ b/src/pl/tcl/pltclu--1.0.sql
@@ -1,11 +1,9 @@
 /* src/pl/tcl/pltclu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltclu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltclu;
+CREATE LANGUAGE pltclu
+  HANDLER pltclu_call_handler;

 COMMENT ON LANGUAGE pltclu IS 'PL/TclU untrusted procedural language';
diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
index 13b28b1..af9d115 100644
--- a/doc/src/sgml/ref/create_language.sgml
+++ b/doc/src/sgml/ref/create_language.sgml
@@ -21,9 +21,9 @@ PostgreSQL documentation

  <refsynopsisdiv>
 <synopsis>
-CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable
class="parameter">inline_handler</replaceable>] [ VALIDATOR <replaceable>valfunction</replaceable> ] 
+CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 </synopsis>
  </refsynopsisdiv>

@@ -37,21 +37,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
    defined in this new language.
   </para>

-  <note>
-   <para>
-    As of <productname>PostgreSQL</productname> 9.1, most procedural
-    languages have been made into <quote>extensions</quote>, and should
-    therefore be installed with <xref linkend="sql-createextension"/>
-    not <command>CREATE LANGUAGE</command>.  Direct use of
-    <command>CREATE LANGUAGE</command> should now be confined to
-    extension installation scripts.  If you have a <quote>bare</quote>
-    language in your database, perhaps as a result of an upgrade,
-    you can convert it to an extension using
-    <literal>CREATE EXTENSION <replaceable>langname</replaceable> FROM
-    unpackaged</literal>.
-   </para>
-  </note>
-
   <para>
    <command>CREATE LANGUAGE</command> effectively associates the
    language name with handler function(s) that are responsible for executing
@@ -60,53 +45,32 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   There are two forms of the <command>CREATE LANGUAGE</command> command.
-   In the first form, the user supplies just the name of the desired
-   language, and the <productname>PostgreSQL</productname> server consults
-   the <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
-   system catalog to determine the correct parameters.  In the second form,
-   the user supplies the language parameters along with the language name.
-   The second form can be used to create a language that is not defined in
-   <structname>pg_pltemplate</structname>, but this approach is considered obsolescent.
-  </para>
-
-  <para>
-   When the server finds an entry in the <structname>pg_pltemplate</structname> catalog
-   for the given language name, it will use the catalog data even if the
-   command includes language parameters.  This behavior simplifies loading of
-   old dump files, which are likely to contain out-of-date information
-   about language support functions.
+   <command>CREATE OR REPLACE LANGUAGE</command> will either create a
+   new language, or replace an existing definition.  If the language
+   already exists, its parameters are updated according to the command,
+   but the language's ownership and permissions settings do not change,
+   and any existing functions written in the language are assumed to still
+   be valid.
   </para>

   <para>
-   Ordinarily, the user must have the
+   One must have the
    <productname>PostgreSQL</productname> superuser privilege to
-   register a new language.  However, the owner of a database can register
-   a new language within that database if the language is listed in
-   the <structname>pg_pltemplate</structname> catalog and is marked
-   as allowed to be created by database owners (<structfield>tmpldbacreate</structfield>
-   is true).  The default is that trusted languages can be created
-   by database owners, but this can be adjusted by superusers by modifying
-   the contents of <structname>pg_pltemplate</structname>.
-   The creator of a language becomes its owner and can later
-   drop it, rename it, or assign it to a new owner.
+   register a new language or change an existing language's parameters.
+   However, once the language is created it is valid to assign ownership of
+   it to a non-superuser, who may then drop it, change its permissions,
+   rename it, or assign it to a new owner.  (Do not, however, assign
+   ownership of the underlying C functions to a non-superuser; that would
+   create a privilege escalation path for that user.)
   </para>

   <para>
-   <command>CREATE OR REPLACE LANGUAGE</command> will either create a
-   new language, or replace an existing definition.  If the language
-   already exists, its parameters are updated according to the values
-   specified or taken from <structname>pg_pltemplate</structname>,
-   but the language's ownership and permissions settings do not change,
-   and any existing functions written in the language are assumed to still
-   be valid.  In addition to the normal privilege requirements for creating
-   a language, the user must be superuser or owner of the existing language.
-   The <literal>REPLACE</literal> case is mainly meant to be used to
-   ensure that the language exists.  If the language has a
-   <structname>pg_pltemplate</structname> entry then <literal>REPLACE</literal>
-   will not actually change anything about an existing definition, except in
-   the unusual case where the <structname>pg_pltemplate</structname> entry
-   has been modified since the language was created.
+   The form of <command>CREATE LANGUAGE</command> that does not supply
+   any handler function is obsolete.  For backwards compatibility with
+   old dump files, it is interpreted as <command>CREATE EXTENSION</command>.
+   That will work if the language has been packaged into an extension of
+   the same name, which is the conventional way to set up procedural
+   languages.
   </para>
  </refsect1>

@@ -218,12 +182,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
      </listitem>
     </varlistentry>
    </variablelist>
-
-  <para>
-   The <literal>TRUSTED</literal> option and the support function name(s) are
-   ignored if the server has an entry for the specified language
-   name in <structname>pg_pltemplate</structname>.
-  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-notes">
@@ -255,18 +213,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   The call handler function, the inline handler function (if any),
-   and the validator function (if any)
-   must already exist if the server does not have an entry for the language
-   in <structname>pg_pltemplate</structname>.  But when there is an entry,
-   the functions need not already exist;
-   they will be automatically defined if not present in the database.
-   (This might result in <command>CREATE LANGUAGE</command> failing, if the
-   shared library that implements the language is not available in
-   the installation.)
-  </para>
-
-  <para>
    In <productname>PostgreSQL</productname> versions before 7.3, it was
    necessary to declare handler functions as returning the placeholder
    type <type>opaque</type>, rather than <type>language_handler</type>.
@@ -281,23 +227,20 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   <title>Examples</title>

   <para>
-   The preferred way of creating any of the standard procedural languages
-   is just:
-<programlisting>
-CREATE LANGUAGE plperl;
-</programlisting>
-  </para>
-
-  <para>
-   For a language not known in the <structname>pg_pltemplate</structname> catalog, a
-   sequence such as this is needed:
+   A minimal sequence for creating a new procedural language is:
 <programlisting>
 CREATE FUNCTION plsample_call_handler() RETURNS language_handler
     AS '$libdir/plsample'
     LANGUAGE C;
 CREATE LANGUAGE plsample
     HANDLER plsample_call_handler;
-</programlisting></para>
+</programlisting>
+   Typically that would be written in an extension's creation script,
+   and users would do this to install the extension:
+<programlisting>
+CREATE EXTENSION plsample;
+</programlisting>
+  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-compat">
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 01de398..ddd46f4 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2277,6 +2277,64 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 }

 /*
+ * Test whether the given extension exists (not whether it's installed)
+ *
+ * This checks for the existence of a matching control file in the extension
+ * directory.  That's not a bulletproof check, since the file might be
+ * invalid, but this is only used for hints so it doesn't have to be 100%
+ * right.
+ */
+bool
+extension_file_exists(const char *extensionName)
+{
+    bool        result = false;
+    char       *location;
+    DIR           *dir;
+    struct dirent *de;
+
+    location = get_extension_control_directory();
+    dir = AllocateDir(location);
+
+    /*
+     * If the control directory doesn't exist, we want to silently return
+     * false.  Any other error will be reported by ReadDir.
+     */
+    if (dir == NULL && errno == ENOENT)
+    {
+        /* do nothing */
+    }
+    else
+    {
+        while ((de = ReadDir(dir, location)) != NULL)
+        {
+            char       *extname;
+
+            if (!is_extension_control_filename(de->d_name))
+                continue;
+
+            /* extract extension name from 'name.control' filename */
+            extname = pstrdup(de->d_name);
+            *strrchr(extname, '.') = '\0';
+
+            /* ignore it if it's an auxiliary control file */
+            if (strstr(extname, "--"))
+                continue;
+
+            /* done if it matches request */
+            if (strcmp(extname, extensionName) == 0)
+            {
+                result = true;
+                break;
+            }
+        }
+
+        FreeDir(dir);
+    }
+
+    return result;
+}
+
+/*
  * Convert a list of extension names to a name[] Datum
  */
 static Datum
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index c31c57e..0f40c9e 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/extension.h"
 #include "commands/proclang.h"
 #include "executor/execdesc.h"
 #include "executor/executor.h"
@@ -991,7 +992,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_OBJECT),
                  errmsg("language \"%s\" does not exist", language),
-                 (PLTemplateExists(language) ?
+                 (extension_file_exists(language) ?
                   errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

     languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
@@ -2225,7 +2226,7 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_OBJECT),
                  errmsg("language \"%s\" does not exist", language),
-                 (PLTemplateExists(language) ?
+                 (extension_file_exists(language) ?
                   errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

     languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index cdff43d..9d72edb 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -13,329 +13,110 @@
  */
 #include "postgres.h"

-#include "access/genam.h"
-#include "access/htup_details.h"
 #include "access/table.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
-#include "catalog/pg_authid.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
-#include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/proclang.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
-#include "parser/parser.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
-#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"


-typedef struct
-{
-    bool        tmpltrusted;    /* trusted? */
-    bool        tmpldbacreate;    /* db owner allowed to create? */
-    char       *tmplhandler;    /* name of handler function */
-    char       *tmplinline;        /* name of anonymous-block handler, or NULL */
-    char       *tmplvalidator;    /* name of validator function, or NULL */
-    char       *tmpllibrary;    /* path of shared library */
-} PLTemplate;
-
-static ObjectAddress create_proc_lang(const char *languageName, bool replace,
-                                      Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                                      Oid valOid, bool trusted);
-static PLTemplate *find_language_template(const char *languageName);
-
 /*
  * CREATE LANGUAGE
  */
 ObjectAddress
 CreateProceduralLanguage(CreatePLangStmt *stmt)
 {
-    PLTemplate *pltemplate;
-    ObjectAddress tmpAddr;
+    const char *languageName = stmt->plname;
+    Oid            languageOwner = GetUserId();
     Oid            handlerOid,
                 inlineOid,
                 valOid;
     Oid            funcrettype;
     Oid            funcargtypes[1];
+    Relation    rel;
+    TupleDesc    tupDesc;
+    Datum        values[Natts_pg_language];
+    bool        nulls[Natts_pg_language];
+    bool        replaces[Natts_pg_language];
+    NameData    langname;
+    HeapTuple    oldtup;
+    HeapTuple    tup;
+    Oid            langoid;
+    bool        is_update;
+    ObjectAddress myself,
+                referenced;

     /*
-     * If we have template information for the language, ignore the supplied
-     * parameters (if any) and use the template information.
+     * Check permission
      */
-    if ((pltemplate = find_language_template(stmt->plname)) != NULL)
-    {
-        List       *funcname;
-
-        /*
-         * Give a notice if we are ignoring supplied parameters.
-         */
-        if (stmt->plhandler)
-            ereport(NOTICE,
-                    (errmsg("using pg_pltemplate information instead of CREATE LANGUAGE parameters")));
-
-        /*
-         * Check permission
-         */
-        if (!superuser())
-        {
-            if (!pltemplate->tmpldbacreate)
-                ereport(ERROR,
-                        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                         errmsg("must be superuser to create procedural language \"%s\"",
-                                stmt->plname)));
-            if (!pg_database_ownercheck(MyDatabaseId, GetUserId()))
-                aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
-                               get_database_name(MyDatabaseId));
-        }
-
-        /*
-         * Find or create the handler function, which we force to be in the
-         * pg_catalog schema.  If already present, it must have the correct
-         * return type.
-         */
-        funcname = SystemFuncName(pltemplate->tmplhandler);
-        handlerOid = LookupFuncName(funcname, 0, NULL, true);
-        if (OidIsValid(handlerOid))
-        {
-            funcrettype = get_func_rettype(handlerOid);
-            if (funcrettype != LANGUAGE_HANDLEROID)
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(funcname), "language_handler")));
-        }
-        else
-        {
-            tmpAddr = ProcedureCreate(pltemplate->tmplhandler,
-                                      PG_CATALOG_NAMESPACE,
-                                      false,    /* replace */
-                                      false,    /* returnsSet */
-                                      LANGUAGE_HANDLEROID,
-                                      BOOTSTRAP_SUPERUSERID,
-                                      ClanguageId,
-                                      F_FMGR_C_VALIDATOR,
-                                      pltemplate->tmplhandler,
-                                      pltemplate->tmpllibrary,
-                                      PROKIND_FUNCTION,
-                                      false,    /* security_definer */
-                                      false,    /* isLeakProof */
-                                      false,    /* isStrict */
-                                      PROVOLATILE_VOLATILE,
-                                      PROPARALLEL_UNSAFE,
-                                      buildoidvector(funcargtypes, 0),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      NIL,
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      InvalidOid,
-                                      1,
-                                      0);
-            handlerOid = tmpAddr.objectId;
-        }
-
-        /*
-         * Likewise for the anonymous block handler, if required; but we don't
-         * care about its return type.
-         */
-        if (pltemplate->tmplinline)
-        {
-            funcname = SystemFuncName(pltemplate->tmplinline);
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(inlineOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplinline,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplinline,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                inlineOid = tmpAddr.objectId;
-            }
-        }
-        else
-            inlineOid = InvalidOid;
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 errmsg("must be superuser to create custom procedural language")));

+    /*
+     * Lookup the PL handler function and check that it is of the expected
+     * return type
+     */
+    Assert(stmt->plhandler);
+    handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false);
+    funcrettype = get_func_rettype(handlerOid);
+    if (funcrettype != LANGUAGE_HANDLEROID)
+    {
         /*
-         * Likewise for the validator, if required; but we don't care about
-         * its return type.
+         * We allow OPAQUE just so we can load old dump files.  When we see a
+         * handler function declared OPAQUE, change it to LANGUAGE_HANDLER.
+         * (This is probably obsolete and removable?)
          */
-        if (pltemplate->tmplvalidator)
+        if (funcrettype == OPAQUEOID)
         {
-            funcname = SystemFuncName(pltemplate->tmplvalidator);
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(valOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplvalidator,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplvalidator,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                valOid = tmpAddr.objectId;
-            }
+            ereport(WARNING,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("changing return type of function %s from %s to %s",
+                            NameListToString(stmt->plhandler),
+                            "opaque", "language_handler")));
+            SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
         }
         else
-            valOid = InvalidOid;
+            ereport(ERROR,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("function %s must return type %s",
+                            NameListToString(stmt->plhandler), "language_handler")));
+    }

-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, pltemplate->tmpltrusted);
+    /* validate the inline function */
+    if (stmt->plinline)
+    {
+        funcargtypes[0] = INTERNALOID;
+        inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
     else
-    {
-        /*
-         * No template, so use the provided information.  If there's no
-         * handler clause, the user is trying to rely on a template that we
-         * don't have, so complain accordingly.
-         */
-        if (!stmt->plhandler)
-            ereport(ERROR,
-                    (errcode(ERRCODE_UNDEFINED_OBJECT),
-                     errmsg("unsupported language \"%s\"",
-                            stmt->plname),
-                     errhint("The supported languages are listed in the pg_pltemplate system catalog.")));
+        inlineOid = InvalidOid;

-        /*
-         * Check permission
-         */
-        if (!superuser())
-            ereport(ERROR,
-                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                     errmsg("must be superuser to create custom procedural language")));
-
-        /*
-         * Lookup the PL handler function and check that it is of the expected
-         * return type
-         */
-        handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false);
-        funcrettype = get_func_rettype(handlerOid);
-        if (funcrettype != LANGUAGE_HANDLEROID)
-        {
-            /*
-             * We allow OPAQUE just so we can load old dump files.  When we
-             * see a handler function declared OPAQUE, change it to
-             * LANGUAGE_HANDLER.  (This is probably obsolete and removable?)
-             */
-            if (funcrettype == OPAQUEOID)
-            {
-                ereport(WARNING,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("changing return type of function %s from %s to %s",
-                                NameListToString(stmt->plhandler),
-                                "opaque", "language_handler")));
-                SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
-            }
-            else
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(stmt->plhandler), "language_handler")));
-        }
-
-        /* validate the inline function */
-        if (stmt->plinline)
-        {
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            inlineOid = InvalidOid;
-
-        /* validate the validator function */
-        if (stmt->plvalidator)
-        {
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            valOid = InvalidOid;
-
-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, stmt->pltrusted);
+    /* validate the validator function */
+    if (stmt->plvalidator)
+    {
+        funcargtypes[0] = OIDOID;
+        valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
-}
-
-/*
- * Guts of language creation.
- */
-static ObjectAddress
-create_proc_lang(const char *languageName, bool replace,
-                 Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                 Oid valOid, bool trusted)
-{
-    Relation    rel;
-    TupleDesc    tupDesc;
-    Datum        values[Natts_pg_language];
-    bool        nulls[Natts_pg_language];
-    bool        replaces[Natts_pg_language];
-    NameData    langname;
-    HeapTuple    oldtup;
-    HeapTuple    tup;
-    Oid            langoid;
-    bool        is_update;
-    ObjectAddress myself,
-                referenced;
+    else
+        valOid = InvalidOid;

+    /* ok to create it */
     rel = table_open(LanguageRelationId, RowExclusiveLock);
     tupDesc = RelationGetDescr(rel);

@@ -348,7 +129,7 @@ create_proc_lang(const char *languageName, bool replace,
     values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname);
     values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner);
     values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true);
-    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(trusted);
+    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(stmt->pltrusted);
     values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
     values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
     values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
@@ -362,13 +143,17 @@ create_proc_lang(const char *languageName, bool replace,
         Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup);

         /* There is one; okay to replace it? */
-        if (!replace)
+        if (!stmt->replace)
             ereport(ERROR,
                     (errcode(ERRCODE_DUPLICATE_OBJECT),
                      errmsg("language \"%s\" already exists", languageName)));
+
+        /* This is currently pointless, since we already checked superuser */
+#ifdef NOT_USED
         if (!pg_language_ownercheck(oldform->oid, languageOwner))
             aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE,
                            languageName);
+#endif

         /*
          * Do not change existing oid, ownership or permissions.  Note
@@ -451,83 +236,6 @@ create_proc_lang(const char *languageName, bool replace,
 }

 /*
- * Look to see if we have template information for the given language name.
- */
-static PLTemplate *
-find_language_template(const char *languageName)
-{
-    PLTemplate *result;
-    Relation    rel;
-    SysScanDesc scan;
-    ScanKeyData key;
-    HeapTuple    tup;
-
-    rel = table_open(PLTemplateRelationId, AccessShareLock);
-
-    ScanKeyInit(&key,
-                Anum_pg_pltemplate_tmplname,
-                BTEqualStrategyNumber, F_NAMEEQ,
-                CStringGetDatum(languageName));
-    scan = systable_beginscan(rel, PLTemplateNameIndexId, true,
-                              NULL, 1, &key);
-
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_pltemplate tmpl = (Form_pg_pltemplate) GETSTRUCT(tup);
-        Datum        datum;
-        bool        isnull;
-
-        result = (PLTemplate *) palloc0(sizeof(PLTemplate));
-        result->tmpltrusted = tmpl->tmpltrusted;
-        result->tmpldbacreate = tmpl->tmpldbacreate;
-
-        /* Remaining fields are variable-width so we need heap_getattr */
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplhandler,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplhandler = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplinline,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplinline = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplvalidator,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplvalidator = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmpllibrary = TextDatumGetCString(datum);
-
-        /* Ignore template if handler or library info is missing */
-        if (!result->tmplhandler || !result->tmpllibrary)
-            result = NULL;
-    }
-    else
-        result = NULL;
-
-    systable_endscan(scan);
-
-    table_close(rel, AccessShareLock);
-
-    return result;
-}
-
-
-/*
- * This just returns true if we have a valid template for a given language
- */
-bool
-PLTemplateExists(const char *languageName)
-{
-    return (find_language_template(languageName) != NULL);
-}
-
-/*
  * Guts of language dropping.
  */
 void
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ba5916b..1b0edf5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4324,14 +4324,17 @@ NumericOnly_list:    NumericOnly                        { $$ = list_make1($1); }
 CreatePLangStmt:
             CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
             {
-                CreatePLangStmt *n = makeNode(CreatePLangStmt);
-                n->replace = $2;
-                n->plname = $6;
-                /* parameters are all to be supplied by system */
-                n->plhandler = NIL;
-                n->plinline = NIL;
-                n->plvalidator = NIL;
-                n->pltrusted = false;
+                /*
+                 * We now interpret parameterless CREATE LANGUAGE as
+                 * CREATE EXTENSION.  "OR REPLACE" is silently translated
+                 * to "IF NOT EXISTS", which isn't quite the same, but
+                 * seems more useful than throwing an error.  We just
+                 * ignore TRUSTED, as the previous code would have too.
+                 */
+                CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
+                n->if_not_exists = $2;
+                n->extname = $6;
+                n->options = NIL;
                 $$ = (Node *)n;
             }
             | CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 6f10707..7923cdc 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -47,6 +47,7 @@ extern ObjectAddress ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *

 extern Oid    get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
+extern bool extension_file_exists(const char *extensionName);

 extern ObjectAddress AlterExtensionNamespace(const char *extensionName, const char *newschema,
                                              Oid *oldschema);
diff --git a/src/include/commands/proclang.h b/src/include/commands/proclang.h
index 9a4bc75..c70f8ec 100644
--- a/src/include/commands/proclang.h
+++ b/src/include/commands/proclang.h
@@ -1,11 +1,12 @@
-/*
- * src/include/commands/proclang.h
- *
- *-------------------------------------------------------------------------
+/*-------------------------------------------------------------------------
  *
  * proclang.h
  *      prototypes for proclang.c.
  *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/proclang.h
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +18,7 @@

 extern ObjectAddress CreateProceduralLanguage(CreatePLangStmt *stmt);
 extern void DropProceduralLanguageById(Oid langOid);
-extern bool PLTemplateExists(const char *languageName);
+
 extern Oid    get_language_oid(const char *langname, bool missing_ok);

 #endif                            /* PROCLANG_H */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 85ac79f..a10b665 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,11 +226,6 @@
      </row>

      <row>
-      <entry><link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link></entry>
-      <entry>template data for procedural languages</entry>
-     </row>
-
-     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -4911,113 +4906,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
  </sect1>


- <sect1 id="catalog-pg-pltemplate">
-  <title><structname>pg_pltemplate</structname></title>
-
-  <indexterm zone="catalog-pg-pltemplate">
-   <primary>pg_pltemplate</primary>
-  </indexterm>
-
-  <para>
-   The catalog <structname>pg_pltemplate</structname> stores
-   <quote>template</quote> information for procedural languages.
-   A template for a language allows the language to be created in a
-   particular database by a simple <command>CREATE LANGUAGE</command> command,
-   with no need to specify implementation details.
-  </para>
-
-  <para>
-   Unlike most system catalogs, <structname>pg_pltemplate</structname>
-   is shared across all databases of a cluster: there is only one
-   copy of <structname>pg_pltemplate</structname> per cluster, not
-   one per database.  This allows the information to be accessible in
-   each database as it is needed.
-  </para>
-
-  <table>
-   <title><structname>pg_pltemplate</structname> Columns</title>
-
-   <tgroup cols="3">
-    <thead>
-     <row>
-      <entry>Name</entry>
-      <entry>Type</entry>
-      <entry>Description</entry>
-     </row>
-    </thead>
-
-    <tbody>
-     <row>
-      <entry><structfield>tmplname</structfield></entry>
-      <entry><type>name</type></entry>
-      <entry>Name of the language this template is for</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpltrusted</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language is considered trusted</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpldbacreate</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language may be created by a database owner</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplhandler</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of call handler function</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplinline</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of anonymous-block handler function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplvalidator</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of validator function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpllibrary</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Path of shared library that implements language</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplacl</structfield></entry>
-      <entry><type>aclitem[]</type></entry>
-      <entry>Access privileges for template (not actually used)</entry>
-     </row>
-
-    </tbody>
-   </tgroup>
-  </table>
-
-  <para>
-   There are not currently any commands that manipulate procedural language
-   templates; to change the built-in information, a superuser must modify
-   the table using ordinary <command>INSERT</command>, <command>DELETE</command>,
-   or <command>UPDATE</command> commands.
-  </para>
-
-  <note>
-   <para>
-    It is likely that <structname>pg_pltemplate</structname> will be removed in some
-    future release of <productname>PostgreSQL</productname>, in favor of
-    keeping this knowledge about procedural languages in their respective
-    extension installation scripts.
-   </para>
-  </note>
-
- </sect1>
-
-
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>

diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 7bdaf76..1921915 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -153,7 +153,7 @@
      <para>
       Daredevils, who want to build a Python-3-only operating system
       environment, can change the contents of
-      <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
+      <literal>plpythonu</literal>'s extension control and script files
       to make <literal>plpythonu</literal> be equivalent
       to <literal>plpython3u</literal>, keeping in mind that this
       would make their installation incompatible with most of the rest
diff --git a/doc/src/sgml/xplang.sgml b/doc/src/sgml/xplang.sgml
index 60e0430..e647e2d 100644
--- a/doc/src/sgml/xplang.sgml
+++ b/doc/src/sgml/xplang.sgml
@@ -190,7 +190,7 @@ CREATE FUNCTION plperl_call_handler() RETURNS language_handler AS

 <programlisting>
 CREATE FUNCTION plperl_inline_handler(internal) RETURNS void AS
-    '$libdir/plperl' LANGUAGE C;
+    '$libdir/plperl' LANGUAGE C STRICT;

 CREATE FUNCTION plperl_validator(oid) RETURNS void AS
     '$libdir/plperl' LANGUAGE C STRICT;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d5da81c..f8f0b48 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -58,7 +58,7 @@ CATALOG_HEADERS := \
     pg_statistic_ext.h pg_statistic_ext_data.h \
     pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
     pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
-    pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
+    pg_database.h pg_db_role_setting.h pg_tablespace.h \
     pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
     pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
     pg_ts_parser.h pg_ts_template.h pg_extension.h \
@@ -84,7 +84,7 @@ POSTGRES_BKI_DATA = $(addprefix $(top_srcdir)/src/include/catalog/,\
     pg_cast.dat pg_class.dat pg_collation.dat pg_conversion.dat \
     pg_database.dat pg_language.dat \
     pg_namespace.dat pg_opclass.dat pg_operator.dat pg_opfamily.dat \
-    pg_pltemplate.dat pg_proc.dat pg_range.dat pg_tablespace.dat \
+    pg_proc.dat pg_range.dat pg_tablespace.dat \
     pg_ts_config.dat pg_ts_config_map.dat pg_ts_dict.dat pg_ts_parser.dat \
     pg_ts_template.dat pg_type.dat \
     )
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 16cb6d8..7d6acae 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -33,7 +33,6 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
 #include "catalog/pg_shdescription.h"
@@ -242,7 +241,6 @@ IsSharedRelation(Oid relationId)
     if (relationId == AuthIdRelationId ||
         relationId == AuthMemRelationId ||
         relationId == DatabaseRelationId ||
-        relationId == PLTemplateRelationId ||
         relationId == SharedDescriptionRelationId ||
         relationId == SharedDependRelationId ||
         relationId == SharedSecLabelRelationId ||
@@ -258,7 +256,6 @@ IsSharedRelation(Oid relationId)
         relationId == AuthMemMemRoleIndexId ||
         relationId == DatabaseNameIndexId ||
         relationId == DatabaseOidIndexId ||
-        relationId == PLTemplateNameIndexId ||
         relationId == SharedDescriptionObjIndexId ||
         relationId == SharedDependDependerIndexId ||
         relationId == SharedDependReferenceIndexId ||
@@ -278,8 +275,6 @@ IsSharedRelation(Oid relationId)
         relationId == PgDatabaseToastIndex ||
         relationId == PgDbRoleSettingToastTable ||
         relationId == PgDbRoleSettingToastIndex ||
-        relationId == PgPlTemplateToastTable ||
-        relationId == PgPlTemplateToastIndex ||
         relationId == PgReplicationOriginToastTable ||
         relationId == PgReplicationOriginToastIndex ||
         relationId == PgShdescriptionToastTable ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 799b698..ec3e2c6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11396,9 +11396,9 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
     }

     /*
-     * If the functions are dumpable then emit a traditional CREATE LANGUAGE
-     * with parameters.  Otherwise, we'll write a parameterless command, which
-     * will rely on data from pg_pltemplate.
+     * If the functions are dumpable then emit a complete CREATE LANGUAGE with
+     * parameters.  Otherwise, we'll write a parameterless command, which will
+     * be interpreted as CREATE EXTENSION.
      */
     useParams = (funcInfo != NULL &&
                  (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
@@ -11431,11 +11431,11 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
         /*
          * If not dumping parameters, then use CREATE OR REPLACE so that the
          * command will not fail if the language is preinstalled in the target
-         * database.  We restrict the use of REPLACE to this case so as to
-         * eliminate the risk of replacing a language with incompatible
-         * parameter settings: this command will only succeed at all if there
-         * is a pg_pltemplate entry, and if there is one, the existing entry
-         * must match it too.
+         * database.
+         *
+         * Modern servers will interpret this as CREATE EXTENSION IF NOT
+         * EXISTS; perhaps we should emit that instead?  But it might just add
+         * confusion.
          */
         appendPQExpBuffer(defqry, "CREATE OR REPLACE PROCEDURAL LANGUAGE %s",
                           qlanname);
diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c
index 28638e9..d163cb2 100644
--- a/src/bin/pg_upgrade/function.c
+++ b/src/bin/pg_upgrade/function.c
@@ -214,13 +214,9 @@ check_loadable_libraries(void)
              * plpython2u language was created with library name plpython2.so
              * as a symbolic link to plpython.so.  In Postgres 9.1, only the
              * plpython2.so library was created, and both plpythonu and
-             * plpython2u pointing to it.  For this reason, any reference to
+             * plpython2u point to it.  For this reason, any reference to
              * library name "plpython" in an old PG <= 9.1 cluster must look
              * for "plpython2" in the new cluster.
-             *
-             * For this case, we could check pg_pltemplate, but that only
-             * works for languages, and does not help with function shared
-             * objects, so we just do a general fix.
              */
             if (GET_MAJOR_VERSION(old_cluster.major_version) < 901 &&
                 strcmp(lib, "$libdir/plpython") == 0)
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 13e07fc..8be3038 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -206,9 +206,6 @@ DECLARE_UNIQUE_INDEX(pg_opfamily_am_name_nsp_index, 2754, on pg_opfamily using b
 DECLARE_UNIQUE_INDEX(pg_opfamily_oid_index, 2755, on pg_opfamily using btree(oid oid_ops));
 #define OpfamilyOidIndexId    2755

-DECLARE_UNIQUE_INDEX(pg_pltemplate_name_index, 1137, on pg_pltemplate using btree(tmplname name_ops));
-#define PLTemplateNameIndexId  1137
-
 DECLARE_UNIQUE_INDEX(pg_proc_oid_index, 2690, on pg_proc using btree(oid oid_ops));
 #define ProcedureOidIndexId  2690
 DECLARE_UNIQUE_INDEX(pg_proc_proname_args_nsp_index, 2691, on pg_proc using btree(proname name_ops, proargtypes
oidvector_ops,pronamespace oid_ops)); 
diff --git a/src/include/catalog/pg_pltemplate.dat b/src/include/catalog/pg_pltemplate.dat
deleted file mode 100644
index 1c96b30..0000000
--- a/src/include/catalog/pg_pltemplate.dat
+++ /dev/null
@@ -1,51 +0,0 @@
-#----------------------------------------------------------------------
-#
-# pg_pltemplate.dat
-#    Initial contents of the pg_pltemplate system catalog.
-#
-# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
-# Portions Copyright (c) 1994, Regents of the University of California
-#
-# src/include/catalog/pg_pltemplate.dat
-#
-#----------------------------------------------------------------------
-
-[
-
-{ tmplname => 'plpgsql', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plpgsql_call_handler', tmplinline => 'plpgsql_inline_handler',
-  tmplvalidator => 'plpgsql_validator', tmpllibrary => '$libdir/plpgsql',
-  tmplacl => '_null_' },
-{ tmplname => 'pltcl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'pltcl_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'pltclu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'pltclu_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plperl_call_handler', tmplinline => 'plperl_inline_handler',
-  tmplvalidator => 'plperl_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperlu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plperlu_call_handler', tmplinline => 'plperlu_inline_handler',
-  tmplvalidator => 'plperlu_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plpythonu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython_call_handler',
-  tmplinline => 'plpython_inline_handler',
-  tmplvalidator => 'plpython_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython2u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython2_call_handler',
-  tmplinline => 'plpython2_inline_handler',
-  tmplvalidator => 'plpython2_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython3u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython3_call_handler',
-  tmplinline => 'plpython3_inline_handler',
-  tmplvalidator => 'plpython3_validator', tmpllibrary => '$libdir/plpython3',
-  tmplacl => '_null_' },
-
-]
diff --git a/src/include/catalog/pg_pltemplate.h b/src/include/catalog/pg_pltemplate.h
deleted file mode 100644
index b930730..0000000
--- a/src/include/catalog/pg_pltemplate.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * pg_pltemplate.h
- *      definition of the "PL template" system catalog (pg_pltemplate)
- *
- *
- * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/catalog/pg_pltemplate.h
- *
- * NOTES
- *      The Catalog.pm module reads this file and derives schema
- *      information.
- *
- *-------------------------------------------------------------------------
- */
-#ifndef PG_PLTEMPLATE_H
-#define PG_PLTEMPLATE_H
-
-#include "catalog/genbki.h"
-#include "catalog/pg_pltemplate_d.h"
-
-/* ----------------
- *        pg_pltemplate definition.  cpp turns this into
- *        typedef struct FormData_pg_pltemplate
- * ----------------
- */
-CATALOG(pg_pltemplate,1136,PLTemplateRelationId) BKI_SHARED_RELATION
-{
-    NameData    tmplname;        /* name of PL */
-    bool        tmpltrusted;    /* PL is trusted? */
-    bool        tmpldbacreate;    /* PL is installable by db owner? */
-
-#ifdef CATALOG_VARLEN            /* variable-length fields start here */
-    text        tmplhandler BKI_FORCE_NOT_NULL; /* name of call handler
-                                                 * function */
-    text        tmplinline;        /* name of anonymous-block handler, or NULL */
-    text        tmplvalidator;    /* name of validator function, or NULL */
-    text        tmpllibrary BKI_FORCE_NOT_NULL; /* path of shared library */
-    aclitem        tmplacl[1];        /* access privileges for template */
-#endif
-} FormData_pg_pltemplate;
-
-/* ----------------
- *        Form_pg_pltemplate corresponds to a pointer to a row with
- *        the format of pg_pltemplate relation.
- * ----------------
- */
-typedef FormData_pg_pltemplate *Form_pg_pltemplate;
-
-#endif                            /* PG_PLTEMPLATE_H */
diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h
index 3281dbd..51491c4 100644
--- a/src/include/catalog/toasting.h
+++ b/src/include/catalog/toasting.h
@@ -86,9 +86,6 @@ DECLARE_TOAST(pg_database, 4177, 4178);
 DECLARE_TOAST(pg_db_role_setting, 2966, 2967);
 #define PgDbRoleSettingToastTable 2966
 #define PgDbRoleSettingToastIndex 2967
-DECLARE_TOAST(pg_pltemplate, 4179, 4180);
-#define PgPlTemplateToastTable 4179
-#define PgPlTemplateToastIndex 4180
 DECLARE_TOAST(pg_replication_origin, 4181, 4182);
 #define PgReplicationOriginToastTable 4181
 #define PgReplicationOriginToastIndex 4182
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index faaec55..882d69e 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -29,7 +29,7 @@
  */

 #if PY_MAJOR_VERSION >= 3
-/* Use separate names to avoid clash in pg_pltemplate */
+/* Use separate names to reduce confusion */
 #define plpython_validator plpython3_validator
 #define plpython_call_handler plpython3_call_handler
 #define plpython_inline_handler plpython3_inline_handler
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 070de78..f0cd40b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -134,7 +134,6 @@ pg_opclass|t
 pg_operator|t
 pg_opfamily|t
 pg_partitioned_table|t
-pg_pltemplate|t
 pg_policy|t
 pg_proc|t
 pg_publication|t