diff options
author | Tom Lane | 2020-01-29 23:42:43 +0000 |
---|---|---|
committer | Tom Lane | 2020-01-29 23:42:43 +0000 |
commit | 50fc694e43742ce3d04a5e9f708432cb022c5f0d (patch) | |
tree | 590d1724a529f8a80294c1f9d0bfe3c7ae9ec942 /src | |
parent | 166ab9c8d343b51e6838d7b59194d32a0019242f (diff) |
Invent "trusted" extensions, and remove the pg_pltemplate catalog.
This patch creates a new extension property, "trusted". An extension
that's marked that way in its control file can be installed by a
non-superuser who has the CREATE privilege on the current database,
even if the extension contains objects that normally would have to be
created by a superuser. The objects within the extension will (by
default) be owned by the bootstrap superuser, but the extension itself
will be owned by the calling user. This allows replicating the old
behavior around trusted procedural languages, without all the
special-case logic in CREATE LANGUAGE. We have, however, chosen to
loosen the rules slightly: formerly, only a database owner could take
advantage of the special case that allowed installation of a trusted
language, but now anyone who has CREATE privilege can do so.
Having done that, we can delete the pg_pltemplate catalog, moving the
knowledge it contained into the extension script files for the various
PLs. This ends up being no change at all for the in-core PLs, but it is
a large step forward for external PLs: they can now have the same ease
of installation as core PLs do. The old "trusted PL" behavior was only
available to PLs that had entries in pg_pltemplate, but now any
extension can be marked trusted if appropriate.
This also removes one of the stumbling blocks for our Python 2 -> 3
migration, since the association of "plpythonu" with Python 2 is no
longer hard-wired into pg_pltemplate's initial contents. Exactly where
we go from here on that front remains to be settled, but one problem
is fixed.
Patch by me, reviewed by Peter Eisentraut, Stephen Frost, and others.
Discussion: https://postgr.es/m/[email protected]
Diffstat (limited to 'src')
34 files changed, 491 insertions, 581 deletions
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index d5da81c8016..f8f0b4841c3 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 16cb6d88714..7d6acaed92c 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/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index c9e75f43705..c9e60600357 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 01de398dcb3..ddd46f4e2f5 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 */ @@ -2198,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 c31c57e5e9a..0f40c9ee27b 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 cdff43d3ce7..9d72edbfec5 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 ba5916b4d27..1b0edf5d3d1 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 799b6988b7a..ec3e2c63b0c 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 28638e951ee..d163cb2dde7 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/catversion.h b/src/include/catalog/catversion.h index 249b1f5a341..b520c050839 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202001251 +#define CATALOG_VERSION_NO 202001291 #endif diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 13e07fc314a..8be303870f8 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 1c96b304319..00000000000 --- 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 b930730e481..00000000000 --- 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/pg_proc.dat b/src/include/catalog/pg_proc.dat index bef50c76d9c..22282569075 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/include/catalog/toasting.h b/src/include/catalog/toasting.h index 3281dbd7aaf..51491c45131 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/include/commands/extension.h b/src/include/commands/extension.h index 6f10707b00c..7923cdc250d 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 9a4bc75d120..c70f8ec928e 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/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile index 9b1c5141014..e4d0a0bf32e 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_call plperl_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 00000000000..faeb645c5f8 --- /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 f716ba1c563..5ff31e74e55 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 6faace12fa5..3a2230a151d 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 7efb4fbc5bf..10d75945a16 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 00000000000..ae48feae863 --- /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 ab6fa84ab0d..6e5b990fccc 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 b320227b120..42e764bf36b 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/plpy_main.c b/src/pl/plpython/plpy_main.c index faaec559a22..882d69e14a1 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/pl/plpython/plpython2u--1.0.sql b/src/pl/plpython/plpython2u--1.0.sql index 661cc66a891..69f74775678 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 c0d6ea82c2d..ba2e6ac076b 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 4a3e64aac50..4c6f7c3f140 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 34a68c8471f..2ed2b920c8e 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 b9dc1b8a138..1568c17e09d 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 e05b470aaf2..fca869f7f2d 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/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 70e1e2f78d1..2ab2115fa1a 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/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 070de78e85c..f0cd40b6607 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 |