agora inbox for pgsql-hackers@postgresql.org
help / color / mirror / Atom feedFrom: Nathan Bossart <nathandbossart@gmail.com>
Subject: [PATCH v1 2/3] Allow building only trusted or untrusted PL/Perl.
Date: Tue, 17 May 2022 13:28:17 -0700
Presently, when the --with-perl configuration option is used, both
trusted and untrusted PL/Perl are built. However, some users may
only want to build one or the other. This change introduces an
optional argument that can be used to do so. If
--with-perl='trusted' is specified, only trusted PL/Perl is built.
If --with-perl='untrusted' is specified, only untrusted PL/Perl is
built. If --with-perl is given without an argument, both trusted
and untrusted PL/Perl are built.
---
configure | 47 ++++++++++++++++++++++---
configure.ac | 32 ++++++++++++++++-
contrib/bool_plperl/Makefile | 24 ++++++++++---
contrib/hstore_plperl/Makefile | 24 ++++++++++---
contrib/jsonb_plperl/Makefile | 24 ++++++++++---
doc/src/sgml/installation.sgml | 23 +++++++++++-
src/Makefile.global.in | 2 ++
src/include/pg_config.h.in | 6 ++++
src/pl/plperl/GNUmakefile | 37 +++++++++++++------
src/pl/plperl/expected/plperl_setup.out | 15 +++-----
src/pl/plperl/expected/plperlu.out | 27 ++++++++++++++
src/pl/plperl/plperl.c | 10 ++++++
src/pl/plperl/sql/plperl_setup.sql | 11 +++---
src/pl/plperl/sql/plperlu.sql | 29 +++++++++++++++
14 files changed, 265 insertions(+), 46 deletions(-)
diff --git a/configure b/configure
index 7dec6b7bf9..faa8b1a2e3 100755
--- a/configure
+++ b/configure
@@ -723,6 +723,8 @@ with_krb_srvnam
krb_srvtab
with_gssapi
with_python
+PERL_UNTRUSTED
+PERL_TRUSTED
with_perl
with_tcl
ICU_LIBS
@@ -1563,7 +1565,8 @@ Optional Packages:
--with-icu build with ICU support
--with-tcl build Tcl modules (PL/Tcl)
--with-tclconfig=DIR tclConfig.sh is in DIR
- --with-perl build Perl modules (PL/Perl)
+ --with-perl[=TRUSTWORTHINESS]
+ build Perl modules (PL/Perl)
--with-python build Python modules (PL/Python)
--with-gssapi build with GSSAPI support
--with-krb-srvnam=NAME default service principal name in Kerberos (GSSAPI)
@@ -8152,26 +8155,62 @@ if test "${with_perl+set}" = set; then :
withval=$with_perl;
case $withval in
yes)
- :
+
+ PERL_TRUSTED=yes
+ PERL_UNTRUSTED=yes
+
;;
no)
:
;;
*)
- as_fn_error $? "no argument expected for --with-perl option" "$LINENO" 5
+ with_perl=yes
+
+ if test "$withval" = trusted ; then
+ PERL_TRUSTED=yes
+ PERL_UNTRUSTED=no
+ elif test "$withval" = untrusted ; then
+ PERL_TRUSTED=no
+ PERL_UNTRUSTED=yes
+ else
+ as_fn_error $? "invalid --with-perl value: argument must be omitted or specified as 'trusted' or 'untrusted'" "$LINENO" 5
+ fi
+
;;
esac
else
with_perl=no
-
fi
+
+if test "$with_perl" = yes; then
+
+ if test "$PERL_TRUSTED" = yes ; then
+
+$as_echo "#define USE_PERL 1" >>confdefs.h
+
+ fi
+ if test "$PERL_UNTRUSTED" = yes ; then
+
+$as_echo "#define USE_PERLU 1" >>confdefs.h
+
+ fi
+
+else
+
+ PERL_TRUSTED=no
+ PERL_UNTRUSTED=no
+
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_perl" >&5
$as_echo "$with_perl" >&6; }
+
+
#
# Optionally build Python modules (PL/Python)
#
diff --git a/configure.ac b/configure.ac
index d093fb88dd..0dd440131b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -823,9 +823,39 @@ PGAC_ARG_REQ(with, tclconfig, [DIR], [tclConfig.sh is in DIR])
# Optionally build Perl modules (PL/Perl)
#
AC_MSG_CHECKING([whether to build Perl modules])
-PGAC_ARG_BOOL(with, perl, no, [build Perl modules (PL/Perl)])
+PGAC_ARG_OPTARG(with, perl,
+[TRUSTWORTHINESS], [build Perl modules (PL/Perl)],
+[
+ PERL_TRUSTED=yes
+ PERL_UNTRUSTED=yes
+],
+[
+ if test "$withval" = trusted ; then
+ PERL_TRUSTED=yes
+ PERL_UNTRUSTED=no
+ elif test "$withval" = untrusted ; then
+ PERL_TRUSTED=no
+ PERL_UNTRUSTED=yes
+ else
+ AC_MSG_ERROR([invalid --with-perl value: argument must be omitted or specified as 'trusted' or 'untrusted'])
+ fi
+],
+[
+ if test "$PERL_TRUSTED" = yes ; then
+ AC_DEFINE([USE_PERL], 1, [Define to 1 to build with trusted Perl support. (--with-perl='trusted')])
+ fi
+ if test "$PERL_UNTRUSTED" = yes ; then
+ AC_DEFINE([USE_PERLU], 1, [Define to 1 to build with untrusted Perl support. (--with-perl='untrusted')])
+ fi
+],
+[
+ PERL_TRUSTED=no
+ PERL_UNTRUSTED=no
+])
AC_MSG_RESULT([$with_perl])
AC_SUBST(with_perl)
+AC_SUBST(PERL_TRUSTED)
+AC_SUBST(PERL_UNTRUSTED)
#
# Optionally build Python modules (PL/Python)
diff --git a/contrib/bool_plperl/Makefile b/contrib/bool_plperl/Makefile
index efe1de986b..ae23e7e3f1 100644
--- a/contrib/bool_plperl/Makefile
+++ b/contrib/bool_plperl/Makefile
@@ -8,10 +8,12 @@ PGFILEDESC = "bool_plperl - bool transform for plperl"
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plperl
-EXTENSION = bool_plperlu bool_plperl
-DATA = bool_plperlu--1.0.sql bool_plperl--1.0.sql
-
-REGRESS = bool_plperl bool_plperlu
+# We do not yet know whether we are building with trusted PL/Perl, untrusted
+# PL/Perl, or both, so for now we assume we are just building trusted PL/Perl.
+# We'll adjust these later on if this assumption was accurate.
+EXTENSION = bool_plperl
+DATA = bool_plperl--1.0.sql
+REGRESS = bool_plperl
ifdef USE_PGXS
PG_CONFIG = pg_config
@@ -37,3 +39,17 @@ endif
# As with plperl we need to include the perl_includespec directory last.
override CPPFLAGS := $(CPPFLAGS) $(perl_embed_ccflags) $(perl_includespec)
+
+# Since we now know whether we are building trusted PL/Perl, untrusted PL/Perl,
+# or both, we should adjust the relevant variables accordingly.
+ifeq ($(PERL_TRUSTED), no)
+ override undefine EXTENSION
+ override undefine DATA
+ override undefine REGRESS
+endif
+
+ifeq ($(PERL_UNTRUSTED), yes)
+ override EXTENSION += bool_plperlu
+ override DATA += bool_plperlu--1.0.sql
+ override REGRESS += bool_plperlu
+endif
diff --git a/contrib/hstore_plperl/Makefile b/contrib/hstore_plperl/Makefile
index 9065f16408..32af5049a9 100644
--- a/contrib/hstore_plperl/Makefile
+++ b/contrib/hstore_plperl/Makefile
@@ -6,11 +6,13 @@ OBJS = \
hstore_plperl.o
PGFILEDESC = "hstore_plperl - hstore transform for plperl"
+# We do not yet know whether we are building with trusted PL/Perl, untrusted
+# PL/Perl, or both, so for now we assume we are just building trusted PL/Perl.
+# We'll adjust these later on if this assumption was accurate.
+EXTENSION = hstore_plperl
+DATA = hstore_plperl--1.0.sql
+REGRESS = hstore_plperl create_transform
-EXTENSION = hstore_plperl hstore_plperlu
-DATA = hstore_plperl--1.0.sql hstore_plperlu--1.0.sql
-
-REGRESS = hstore_plperl hstore_plperlu create_transform
EXTRA_INSTALL = contrib/hstore
ifdef USE_PGXS
@@ -39,3 +41,17 @@ endif
# As with plperl we need to include the perl_includespec directory last.
override CPPFLAGS := $(CPPFLAGS) $(perl_embed_ccflags) $(perl_includespec)
+
+# Since we now know whether we are building trusted PL/Perl, untrusted PL/Perl,
+# or both, we should adjust the relevant variables accordingly.
+ifeq ($(PERL_TRUSTED), no)
+ override undefine EXTENSION
+ override undefine DATA
+ override undefine REGRESS
+endif
+
+ifeq ($(PERL_UNTRUSTED), yes)
+ override EXTENSION += hstore_plperlu
+ override DATA += hstore_plperlu--1.0.sql
+ override REGRESS += hstore_plperlu
+endif
diff --git a/contrib/jsonb_plperl/Makefile b/contrib/jsonb_plperl/Makefile
index ba9480e819..218bd51c9e 100644
--- a/contrib/jsonb_plperl/Makefile
+++ b/contrib/jsonb_plperl/Makefile
@@ -8,10 +8,12 @@ PGFILEDESC = "jsonb_plperl - jsonb transform for plperl"
PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plperl
-EXTENSION = jsonb_plperlu jsonb_plperl
-DATA = jsonb_plperlu--1.0.sql jsonb_plperl--1.0.sql
-
-REGRESS = jsonb_plperl jsonb_plperlu
+# We do not yet know whether we are building with trusted PL/Perl, untrusted
+# PL/Perl, or both, so for now we assume we are just building trusted PL/Perl.
+# We'll adjust these later on if this assumption was accurate.
+EXTENSION = jsonb_plperl
+DATA = jsonb_plperl--1.0.sql
+REGRESS = jsonb_plperl
SHLIB_LINK += $(filter -lm, $(LIBS))
@@ -39,3 +41,17 @@ endif
# As with plperl we need to include the perl_includespec directory last.
override CPPFLAGS := $(CPPFLAGS) $(perl_embed_ccflags) $(perl_includespec)
+
+# Since we now know whether we are building trusted PL/Perl, untrusted PL/Perl,
+# or both, we should adjust the relevant variables accordingly.
+ifeq ($(PERL_TRUSTED), no)
+ override undefine EXTENSION
+ override undefine DATA
+ override undefine REGRESS
+endif
+
+ifeq ($(PERL_UNTRUSTED), yes)
+ override EXTENSION += jsonb_plperlu
+ override DATA += jsonb_plperlu--1.0.sql
+ override REGRESS += jsonb_plperlu
+endif
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index c585078029..4377e9d51a 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -873,10 +873,31 @@ build-postgresql:
</varlistentry>
<varlistentry>
- <term><option>--with-perl</option></term>
+ <term><option>--with-perl<optional>=<replaceable>TRUSTWORTHINESS</replaceable></optional></option></term>
<listitem>
<para>
Build the <application>PL/Perl</application> server-side language.
+ <replaceable>TRUSTWORTHINESS</replaceable> is an optional argument and,
+ if provided, must be one of:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <option>trusted</option> to build only trusted
+ <application>PL/Perl</application>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>untrusted</option> to build only untrusted
+ <application>PL/Perl</application>
+ (<application>PL/PerlU</application>)
+ </para>
+ </listitem>
+ </itemizedlist>
+ <para>
+ If <replaceable>TRUSTWORTHINESS</replaceable> is not specified, both
+ trusted and untrusted <application>PL/Perl</application> will be built.
</para>
</listitem>
</varlistentry>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 051718e4fe..53f367ca7a 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -526,6 +526,8 @@ GENHTML = @GENHTML@
DEF_PGPORT = @default_port@
WANTED_LANGUAGES = @WANTED_LANGUAGES@
+PERL_TRUSTED = @PERL_TRUSTED@
+PERL_UNTRUSTED = @PERL_UNTRUSTED@
##########################################################################
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index cdd742cb55..2779f5f671 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -931,6 +931,12 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to 1 to build with trusted Perl support. (--with-perl='trusted') */
+#undef USE_PERL
+
+/* Define to 1 to build with untrusted Perl support. (--with-perl='untrusted') */
+#undef USE_PERLU
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile
index a2e6410f53..d01201bbac 100644
--- a/src/pl/plperl/GNUmakefile
+++ b/src/pl/plperl/GNUmakefile
@@ -27,8 +27,13 @@ NAME = plperl
OBJS = plperl.o SPI.o Util.o $(WIN32RES)
-DATA = plperl.control plperl--1.0.sql \
- plperlu.control plperlu--1.0.sql
+ifeq ($(PERL_TRUSTED), yes)
+ DATA += plperl.control plperl--1.0.sql
+endif
+
+ifeq ($(PERL_UNTRUSTED), yes)
+ DATA += plperlu.control plperlu--1.0.sql
+endif
PERLCHUNKS = plc_perlboot.pl plc_trusted.pl
@@ -56,14 +61,26 @@ endif # win32
SHLIB_LINK = $(perl_embed_ldflags)
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),)
-ifeq ($(shell $(PERL) -V:usemultiplicity), usemultiplicity='define';)
- REGRESS += plperl_plperlu
+
+ifeq ($(PERL_TRUSTED), yes)
+ REGRESS = plperl_setup plperl plperl_lc plperl_trigger plperl_shared \
+ plperl_elog plperl_util plperl_init plperl_array plperl_call \
+ plperl_transaction
+endif
+
+ifeq ($(PERL_UNTRUSTED), yes)
+ REGRESS += plperlu
+endif
+
+ifeq ($(PERL_TRUSTED), yes)
+ifeq ($(PERL_UNTRUSTED), yes)
+ # if Perl can support two interpreters in one backend,
+ # test plperl-and-plperlu cases
+ ifneq ($(PERL),)
+ ifeq ($(shell $(PERL) -V:usemultiplicity), usemultiplicity='define';)
+ REGRESS += plperl_plperlu
+ endif
+ endif
endif
endif
diff --git a/src/pl/plperl/expected/plperl_setup.out b/src/pl/plperl/expected/plperl_setup.out
index 5234febefd..d0682325b2 100644
--- a/src/pl/plperl/expected/plperl_setup.out
+++ b/src/pl/plperl/expected/plperl_setup.out
@@ -1,18 +1,15 @@
--
--- Install the plperl and plperlu extensions
+-- Install plperl
--
-- 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.
+-- a non-superuser is allowed to install plperl 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
@@ -22,9 +19,6 @@ 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 SCHEMA plperl_setup_scratch;
SET search_path = plperl_setup_scratch;
GRANT ALL ON SCHEMA plperl_setup_scratch TO regress_user2;
@@ -68,6 +62,5 @@ 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.
+-- Now install the version that will be used by subsequent test scripts.
CREATE EXTENSION plperl;
-CREATE EXTENSION plperlu;
diff --git a/src/pl/plperl/expected/plperlu.out b/src/pl/plperl/expected/plperlu.out
index a3edb38497..f759466a90 100644
--- a/src/pl/plperl/expected/plperlu.out
+++ b/src/pl/plperl/expected/plperlu.out
@@ -1,5 +1,32 @@
+-- Before going ahead with the to-be-tested installations, verify that
+-- only superusers can install plperlu.
+CREATE USER regress_user1;
+SET ROLE regress_user1;
+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 plperlu; -- fail
+ERROR: permission denied to create extension "plperlu"
+HINT: Must be superuser to create this extension.
+RESET ROLE;
+DO $$
+begin
+ execute format('revoke create on database %I from regress_user1',
+ current_database());
+end;
+$$;
+DROP ROLE regress_user1;
-- Use ONLY plperlu tests here. For plperl/plerlu combined tests
-- see plperl_plperlu.sql
+CREATE EXTENSION plperlu;
-- This test tests setting on_plperlu_init after loading plperl
LOAD 'plperl';
-- Test plperl.on_plperlu_init gets run
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 9bc6793a30..ff5f9641d5 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -445,6 +445,7 @@ _PG_init(void)
* OK since the worst result would be an error. Your code oughta pass
* use_strict anyway ;-)
*/
+#ifdef USE_PERL
DefineCustomStringVariable("plperl.on_plperl_init",
gettext_noop("Perl initialization code to execute once when plperl is first used."),
NULL,
@@ -452,7 +453,9 @@ _PG_init(void)
NULL,
PGC_SUSET, 0,
NULL, NULL, NULL);
+#endif /* USE_PERL */
+#ifdef USE_PERLU
DefineCustomStringVariable("plperl.on_plperlu_init",
gettext_noop("Perl initialization code to execute once when plperlu is first used."),
NULL,
@@ -460,6 +463,7 @@ _PG_init(void)
NULL,
PGC_SUSET, 0,
NULL, NULL, NULL);
+#endif /* USE_PERLU */
MarkGUCPrefixReserved("plperl");
@@ -2039,6 +2043,7 @@ plperl_validator_internal(PG_FUNCTION_ARGS, bool trusted)
* There are three externally visible pieces to plperl: plperl_call_handler,
* plperl_inline_handler, and plperl_validator.
*/
+#ifdef USE_PERL
PG_FUNCTION_INFO_V1(plperl_call_handler);
@@ -2064,11 +2069,14 @@ plperl_validator(PG_FUNCTION_ARGS)
return plperl_validator_internal(fcinfo, true);
}
+#endif /* USE_PERL */
+
/*
* plperlu likewise requires three externally visible functions:
* plperlu_call_handler, plperlu_inline_handler, and plperlu_validator.
*/
+#ifdef USE_PERLU
PG_FUNCTION_INFO_V1(plperlu_call_handler);
@@ -2095,6 +2103,8 @@ plperlu_validator(PG_FUNCTION_ARGS)
return plperl_validator_internal(fcinfo, false);
}
+#endif /* USE_PERLU */
+
/*
* Uses mkfunc to create a subroutine whose text is
diff --git a/src/pl/plperl/sql/plperl_setup.sql b/src/pl/plperl/sql/plperl_setup.sql
index a89cf56617..eb29cacdac 100644
--- a/src/pl/plperl/sql/plperl_setup.sql
+++ b/src/pl/plperl/sql/plperl_setup.sql
@@ -1,10 +1,10 @@
--
--- Install the plperl and plperlu extensions
+-- Install plperl
--
-- 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.
+-- a non-superuser is allowed to install plperl when suitable permissions
+-- have been granted.
CREATE USER regress_user1;
CREATE USER regress_user2;
@@ -12,7 +12,6 @@ CREATE USER regress_user2;
SET ROLE regress_user1;
CREATE EXTENSION plperl; -- fail
-CREATE EXTENSION plperlu; -- fail
RESET ROLE;
@@ -26,7 +25,6 @@ $$;
SET ROLE regress_user1;
CREATE EXTENSION plperl;
-CREATE EXTENSION plperlu; -- fail
CREATE SCHEMA plperl_setup_scratch;
SET search_path = plperl_setup_scratch;
GRANT ALL ON SCHEMA plperl_setup_scratch TO regress_user2;
@@ -68,6 +66,5 @@ 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.
+-- Now install the version that will be used by subsequent test scripts.
CREATE EXTENSION plperl;
-CREATE EXTENSION plperlu;
diff --git a/src/pl/plperl/sql/plperlu.sql b/src/pl/plperl/sql/plperlu.sql
index be43df5d90..1e050e8237 100644
--- a/src/pl/plperl/sql/plperlu.sql
+++ b/src/pl/plperl/sql/plperlu.sql
@@ -1,5 +1,34 @@
+-- Before going ahead with the to-be-tested installations, verify that
+-- only superusers can install plperlu.
+CREATE USER regress_user1;
+
+SET ROLE regress_user1;
+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 plperlu; -- fail
+RESET ROLE;
+
+DO $$
+begin
+ execute format('revoke create on database %I from regress_user1',
+ current_database());
+end;
+$$;
+
+DROP ROLE regress_user1;
+
-- Use ONLY plperlu tests here. For plperl/plerlu combined tests
-- see plperl_plperlu.sql
+CREATE EXTENSION plperlu;
-- This test tests setting on_plperlu_init after loading plperl
LOAD 'plperl';
--
2.25.1
--ReaqsoxgOBHFXBhH
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v1-0003-Allow-building-only-trusted-or-untrusted-PL-Tcl.patch"
view thread (5+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: nathandbossart@gmail.com
Subject: Re: [PATCH v1 2/3] Allow building only trusted or untrusted PL/Perl.
In-Reply-To: <no-message-id-649255@localhost>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox