diff options
-rw-r--r-- | doc/src/sgml/pltcl.sgml | 14 | ||||
-rw-r--r-- | src/pl/tcl/pltcl.c | 191 |
2 files changed, 137 insertions, 68 deletions
diff --git a/doc/src/sgml/pltcl.sgml b/doc/src/sgml/pltcl.sgml index d0ddddc18b9..aa7231c54db 100644 --- a/doc/src/sgml/pltcl.sgml +++ b/doc/src/sgml/pltcl.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.33.4.1 2005/01/22 23:05:48 momjian Exp $ +$PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.33.4.2 2010/05/13 18:29:45 tgl Exp $ --> <chapter id="pltcl"> @@ -664,8 +664,10 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab It recognizes a special table, <literal>pltcl_modules</>, which is presumed to contain modules of Tcl code. If this table exists, the module <literal>unknown</> is fetched from the table - and loaded into the Tcl interpreter immediately after creating - the interpreter. + and loaded into the Tcl interpreter immediately before the first + execution of a PL/Tcl function in a database session. (This + happens separately for PL/Tcl and PL/TclU, if both are used, + because separate interpreters are used for the two languages.) </para> <para> While the <literal>unknown</> module could actually contain any @@ -692,7 +694,11 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab <para> The tables <literal>pltcl_modules</> and <literal>pltcl_modfuncs</> must be readable by all, but it is wise to make them owned and - writable only by the database administrator. + writable only by the database administrator. As a security + precaution, PL/Tcl will ignore <literal>pltcl_modules</> (and thus, + not attempt to load the <literal>unknown</> module) unless it is + owned by a superuser. But update privileges on this table can be + granted to other users, if you trust them sufficiently. </para> </sect1> diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index a6a3a93eb50..3ee1646a634 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -31,7 +31,7 @@ * ENHANCEMENTS, OR MODIFICATIONS. * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.94.4.3 2010/01/25 01:58:48 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.94.4.4 2010/05/13 18:29:45 tgl Exp $ * **********************************************************************/ @@ -48,15 +48,18 @@ #endif #include "access/heapam.h" +#include "catalog/namespace.h" #include "catalog/pg_language.h" #include "catalog/pg_proc.h" #include "commands/trigger.h" #include "executor/spi.h" #include "fmgr.h" +#include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_type.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" +#include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/typcache.h" @@ -142,7 +145,8 @@ typedef struct pltcl_query_desc * Global data **********************************************************************/ static bool pltcl_pm_init_done = false; -static bool pltcl_be_init_done = false; +static bool pltcl_be_norm_init_done = false; +static bool pltcl_be_safe_init_done = false; static Tcl_Interp *pltcl_hold_interp = NULL; static Tcl_Interp *pltcl_norm_interp = NULL; static Tcl_Interp *pltcl_safe_interp = NULL; @@ -157,9 +161,9 @@ static pltcl_proc_desc *pltcl_current_prodesc = NULL; /********************************************************************** * Forward declarations **********************************************************************/ -static void pltcl_init_all(void); -static void pltcl_init_interp(Tcl_Interp *interp); +static void pltcl_init_interp(Tcl_Interp *interp); +static Tcl_Interp *pltcl_fetch_interp(bool pltrusted); static void pltcl_init_load_unknown(Tcl_Interp *interp); Datum pltcl_call_handler(PG_FUNCTION_ARGS); @@ -277,37 +281,11 @@ pltcl_init(void) } /********************************************************************** - * pltcl_init_all() - Initialize all - **********************************************************************/ -static void -pltcl_init_all(void) -{ - /************************************************************ - * Execute postmaster-startup safe initialization - ************************************************************/ - if (!pltcl_pm_init_done) - pltcl_init(); - - /************************************************************ - * Any other initialization that must be done each time a new - * backend starts: - * - Try to load the unknown procedure from pltcl_modules - ************************************************************/ - if (!pltcl_be_init_done) - { - if (SPI_connect() != SPI_OK_CONNECT) - elog(ERROR, "SPI_connect failed"); - pltcl_init_load_unknown(pltcl_norm_interp); - pltcl_init_load_unknown(pltcl_safe_interp); - if (SPI_finish() != SPI_OK_FINISH) - elog(ERROR, "SPI_finish failed"); - pltcl_be_init_done = true; - } -} - - -/********************************************************************** * pltcl_init_interp() - initialize a Tcl interpreter + * + * The work done here must be safe to do in the postmaster process, + * in case the pltcl library is preloaded in the postmaster. Note + * that this is applied separately to the "normal" and "safe" interpreters. **********************************************************************/ static void pltcl_init_interp(Tcl_Interp *interp) @@ -334,6 +312,42 @@ pltcl_init_interp(Tcl_Interp *interp) pltcl_SPI_lastoid, NULL, NULL); } +/********************************************************************** + * pltcl_fetch_interp() - fetch the Tcl interpreter to use for a function + * + * This also takes care of any on-first-use initialization required. + * The initialization work done here can't be done in the postmaster, and + * hence is not safe to do at library load time, because it may invoke + * arbitrary user-defined code. + * Note: we assume caller has already connected to SPI. + **********************************************************************/ +static Tcl_Interp * +pltcl_fetch_interp(bool pltrusted) +{ + Tcl_Interp *interp; + + /* On first use, we try to load the unknown procedure from pltcl_modules */ + if (pltrusted) + { + interp = pltcl_safe_interp; + if (!pltcl_be_safe_init_done) + { + pltcl_init_load_unknown(interp); + pltcl_be_safe_init_done = true; + } + } + else + { + interp = pltcl_norm_interp; + if (!pltcl_be_norm_init_done) + { + pltcl_init_load_unknown(interp); + pltcl_be_norm_init_done = true; + } + } + + return interp; +} /********************************************************************** * pltcl_init_load_unknown() - Load the unknown procedure from @@ -342,6 +356,12 @@ pltcl_init_interp(Tcl_Interp *interp) static void pltcl_init_load_unknown(Tcl_Interp *interp) { + Oid relOid; + Relation pmrel; + char *pmrelname, + *nspname; + char *buf; + int buflen; int spi_rc; int tcl_rc; Tcl_DString unknown_src; @@ -351,47 +371,87 @@ pltcl_init_load_unknown(Tcl_Interp *interp) /************************************************************ * Check if table pltcl_modules exists + * + * We allow the table to be found anywhere in the search_path. + * This is for backwards compatibility. To ensure that the table + * is trustworthy, we require it to be owned by a superuser. + * + * this next bit of code is the same as try_relation_openrv(), + * which only exists in 8.4 and up. ************************************************************/ - spi_rc = SPI_execute("select 1 from pg_catalog.pg_class " - "where relname = 'pltcl_modules'", - false, 1); - SPI_freetuptable(SPI_tuptable); - if (spi_rc != SPI_OK_SELECT) - elog(ERROR, "select from pg_class failed"); - if (SPI_processed == 0) + + /* Check for shared-cache-inval messages */ + AcceptInvalidationMessages(); + + /* Look up the appropriate relation using namespace search */ + relOid = RangeVarGetRelid(makeRangeVar(NULL, "pltcl_modules"), true); + + /* Drop out on not-found */ + if (!OidIsValid(relOid)) return; + /* Let relation_open do the rest */ + pmrel = relation_open(relOid, AccessShareLock); + + if (pmrel == NULL) + return; + /* must be table or view, else ignore */ + if (!(pmrel->rd_rel->relkind == RELKIND_RELATION || + pmrel->rd_rel->relkind == RELKIND_VIEW)) + { + relation_close(pmrel, AccessShareLock); + return; + } + /* must be owned by superuser, else ignore */ + if (!superuser_arg(pmrel->rd_rel->relowner)) + { + relation_close(pmrel, AccessShareLock); + return; + } + /* get fully qualified table name for use in select command */ + nspname = get_namespace_name(RelationGetNamespace(pmrel)); + if (!nspname) + elog(ERROR, "cache lookup failed for namespace %u", + RelationGetNamespace(pmrel)); + pmrelname = quote_qualified_identifier(nspname, + RelationGetRelationName(pmrel)); + /************************************************************ - * Read all the row's from it where modname = 'unknown' in - * the order of modseq + * Read all the rows from it where modname = 'unknown', + * in the order of modseq ************************************************************/ - Tcl_DStringInit(&unknown_src); + buflen = strlen(pmrelname) + 100; + buf = (char *) palloc(buflen); + snprintf(buf, buflen, + "select modsrc from %s where modname = 'unknown' order by modseq", + pmrelname); - spi_rc = SPI_execute("select modseq, modsrc from pltcl_modules " - "where modname = 'unknown' " - "order by modseq", - false, 0); + spi_rc = SPI_execute(buf, false, 0); if (spi_rc != SPI_OK_SELECT) elog(ERROR, "select from pltcl_modules failed"); + pfree(buf); + /************************************************************ * If there's nothing, module unknown doesn't exist ************************************************************/ if (SPI_processed == 0) { - Tcl_DStringFree(&unknown_src); SPI_freetuptable(SPI_tuptable); elog(WARNING, "module \"unknown\" not found in pltcl_modules"); + relation_close(pmrel, AccessShareLock); return; } /************************************************************ - * There is a module named unknown. Resemble the + * There is a module named unknown. Reassemble the * source from the modsrc attributes and evaluate * it in the Tcl interpreter ************************************************************/ fno = SPI_fnumber(SPI_tuptable->tupdesc, "modsrc"); + Tcl_DStringInit(&unknown_src); + for (i = 0; i < SPI_processed; i++) { part = SPI_getvalue(SPI_tuptable->vals[i], @@ -405,8 +465,19 @@ pltcl_init_load_unknown(Tcl_Interp *interp) } } tcl_rc = Tcl_GlobalEval(interp, Tcl_DStringValue(&unknown_src)); + Tcl_DStringFree(&unknown_src); SPI_freetuptable(SPI_tuptable); + + if (tcl_rc != TCL_OK) + { + UTF_BEGIN; + elog(ERROR, "could not load module \"unknown\": %s", + UTF_U2E(Tcl_GetStringResult(interp))); + UTF_END; + } + + relation_close(pmrel, AccessShareLock); } @@ -428,9 +499,10 @@ pltcl_call_handler(PG_FUNCTION_ARGS) pltcl_proc_desc *save_prodesc; /* - * Initialize interpreters if first time through + * Initialize interpreters if not done previously */ - pltcl_init_all(); + if (!pltcl_pm_init_done) + pltcl_init(); /* * Ensure that static pointers are saved/restored properly @@ -505,10 +577,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS) pltcl_current_prodesc = prodesc; - if (prodesc->lanpltrusted) - interp = pltcl_safe_interp; - else - interp = pltcl_norm_interp; + interp = pltcl_fetch_interp(prodesc->lanpltrusted); /************************************************************ * Create the tcl command to call the internal @@ -666,10 +735,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS) pltcl_current_prodesc = prodesc; - if (prodesc->lanpltrusted) - interp = pltcl_safe_interp; - else - interp = pltcl_norm_interp; + interp = pltcl_fetch_interp(prodesc->lanpltrusted); tupdesc = trigdata->tg_relation->rd_att; @@ -1090,10 +1156,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) prodesc->lanpltrusted = langStruct->lanpltrusted; ReleaseSysCache(langTup); - if (prodesc->lanpltrusted) - interp = pltcl_safe_interp; - else - interp = pltcl_norm_interp; + interp = pltcl_fetch_interp(prodesc->lanpltrusted); /************************************************************ * Get the required information for input conversion of the |