diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..68dfa7ad
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,39 @@
+*.txt
+*.orig
+*.gdbhistory
+*.swp
+.deps
+.libs
+*.lo
+src/tests/*.diff
+src/tests/*.exp
+src/tests/*.log
+src/tests/*.out
+src/tests/*.sh
+src/tests/*.php
+# Files generated by phpize, configure and make
+src/autom4te.cache
+src/build
+src/modules
+src/acinclude.m4
+src/aclocal.m4
+src/config.guess
+src/config.h
+src/config.h.in
+src/config.log
+src/config.nice
+src/config.status
+src/config.sub
+src/configure
+src/configure.in
+src/install-sh
+src/*.la
+src/ltmain.sh
+src/libtool
+src/Makefile
+src/Makefile.fragments
+src/Makefile.global
+src/Makefile.objects
+src/missing
+src/mkinstalldirs
+src/run-tests.php
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..65c5ca88
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..869f9fbe
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,32 @@
+clean:
+ make -C src clean
+ cd src; phpize --clean
+
+debug:
+ cd src; phpize
+ export CFLAGS="-Wall -Wextra -g3 -ggdb -Wno-unused-function"; cd src; ./configure --enable-snuffleupagus --enable-debug
+ make -C src
+ TEST_PHP_ARGS='-q' REPORT_EXIT_STATUS=1 make -C src test
+
+coverage:
+ cd src; phpize
+ export CFLAGS="--coverage -fprofile-arcs -ftest-coverage -O0"; export LDFLAGS="--coverage"; cd src; ./configure --enable-snuffleupagus --enable-coverage
+ make -C src
+ lcov --base-directory src --directory ./src --zerocounters -q --rc lcov_branch_coverage=1
+ rm -Rf src/COV.html
+ TEST_PHP_ARGS='-q' REPORT_EXIT_STATUS=1 make -C src test
+ lcov --base-directory ./src --directory src -c -o ./src/COV.info --rc lcov_branch_coverage=1 2>/dev/null 1>/dev/null
+ lcov --remove src/COV.info '/usr/*' --remove src/COV.info '*tweetnacl.c' -o src/COV.info --rc lcov_branch_coverage=1 2>/dev/null 1>/dev/null
+ genhtml -o src/COV.html ./src/COV.info --branch-coverage
+
+tests: joomla
+
+joomla:
+ if [ -nd "joomla-cms" ]; then git clone --depth 1 git@github.com:joomla/joomla-cms.git; fi
+ cd joomla-cms; composer install
+ cd joomla-cms; libraries/vendor/phpunit/phpunit/phpunit -d extension=./src/modules/snuffleupagus.so
+
+packages: debian
+
+debian:
+ dpkg-buildpackage -i -us -uc -tc -I -rfakeroot
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..53b9003a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+[![build
+status](https://gitlab.nbs-system.com/secu/snuffleupagus/badges/master/build.svg)](https://gitlab.nbs-system.com/secu/snuffleupagus/commits/master)
+[![coverage
+report](https://gitlab.nbs-system.com/secu/snuffleupagus/badges/master/coverage.svg)](https://gitlab.nbs-system.com/secu/snuffleupagus/commits/master)
+[Documentation]( https://secu.gitlab-pages.nbs-system.com/snuffleupagus/ )
+
+# Code style
+We're using [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to
+ensure a consistent code-style. Please run it with `clang-format -style=google`
+before committing, or even better, use a [pre-commit hook](https://github.com/andrewseidl/githook-clang-format)
+
+# Resources
+- The [writeup]( https://github.com/beberlei/whitewashing.de/blob/master/drafts/porting_extension_to_php7.rst) of [tideway profiler](https://github.com/tideways/php-profiler-extension)'s port to php7
+- [Upgrading to php-ng]( https://wiki.php.net/phpng-upgrading )
+- PHP7 [virtual machine](https://nikic.github.io/2017/04/14/PHP-7-Virtual-machine.html)
diff --git a/config/default.ini b/config/default.ini
new file mode 100644
index 00000000..0f676329
--- /dev/null
+++ b/config/default.ini
@@ -0,0 +1,54 @@
+# Harden the `chmod` function
+sp.disable_functions.function("chmod").param("mode").value_r("^[0-9]{2}[67]$").drop();
+sp.disable_functions.function("chmod").param("mode").value_r("o\\+w$").drop();
+
+# Prevent various `mail`-related vulnerabilities
+sp.disable_functions.function("mail").param("additional_parameters").value_r("\\-").drop();
+
+##Prevent various `include`-related vulnerabilities
+sp.disable_functions.function_r("^(?:require|include)_once$").value_r("\\.(?:php|php7|inc|tpl)$").allow();
+sp.disable_functions.function_r("^require|include$").value_r("\\.(?:php|php7|inc|tpl)$").allow();
+sp.disable_functions.function_r("^(?:require|include)_once$").drop();
+sp.disable_functions.function_r("^require|include$").drop();
+
+# Prevent `system`-related injections
+sp.disable_functions.function("system").param("command").value_r("[$|;&`\\n]").drop();
+sp.disable_functions.function("shell_exec").param("command").value_r("[$|;&`\\n]").drop();
+sp.disable_functions.function("exec").param("command").value_r("[$|;&`\\n]").drop();
+sp.disable_functions.function("proc_open").param("command").value_r("[$|;&`\\n]").drop();
+
+# Prevent runtime modification of interesting things
+sp.disable_functions.function("ini_set").param("var_name").value("assert.active").drop();
+sp.disable_functions.function("ini_set").param("var_name").value("zend.assertions").drop();
+sp.disable_functions.function("ini_set").param("var_name").value("memory_limit").drop();
+sp.disable_functions.function("ini_set").param("var_name").value("include_path").drop();
+sp.disable_functions.function("ini_set").param("var_name").value("open_basedir").drop();
+
+# Detect some backdoors via environnement recon
+sp.disable_functions.function("ini_get").param("var_name").value_r("(?:allow_url_fopen|open_basedir|suhosin)").drop();
+sp.disable_functions.function("function_exists").param("function_name").value_r("(?:eval|exec|system)").drop();
+sp.disable_functions.function("is_callable").param("var").value_r("(?:eval|exec|system)").drop();
+
+# Ghetto sqli hardening
+sp.disable_functions.function_r("mysqli?_query").param("query").value_r("/\\*").drop();
+sp.disable_functions.function_r("mysqli?_query").param("query").value_r("--").drop();
+sp.disable_functions.function_r("mysqli?_query").param("query").value_r("#").drop();
+sp.disable_functions.function_r("mysqli?_query").param("query").value_r(";.*;").drop();
+sp.disable_functions.function_r("mysqli?_query").param("query").value_r("benchmark").drop();
+sp.disable_functions.function_r("mysqli?_query").param("query").value_r("sleep").drop();
+sp.disable_functions.function_r("mysqli?_query").param("query").value_r("information_schema").drop();
+sp.disable_functions.function("PDO::query").param("query").value_r("/\\*").drop();
+sp.disable_functions.function("PDO::query").param("query").value_r("--").drop();
+sp.disable_functions.function("PDO::query").param("query").value_r("#").drop();
+sp.disable_functions.function("PDO::query").param("query").value_r(";.*;").drop();
+sp.disable_functions.function("PDO::query").param("query").value_r("benchmark\\s*\\(").drop();
+sp.disable_functions.function("PDO::query").param("query").value_r("sleep\\s*\\(").drop();
+sp.disable_functions.function("PDO::query").param("query").value_r("information_schema").drop();
+
+# Ghetto sqli detection
+sp.disable_functions.function_r("mysqli?_query").ret("FALSE").drop();
+sp.disable_functions.function_r("PDO::query").ret("FALSE").drop();
+
+#File upload
+sp.disable_functions.function("move_uploaded_file").param("destination").value_r("\\.ph").drop();
+sp.disable_functions.function("move_uploaded_file").param("destination").value_r("\\.ht").drop();
diff --git a/config/examples.ini b/config/examples.ini
new file mode 100644
index 00000000..d7599fb3
--- /dev/null
+++ b/config/examples.ini
@@ -0,0 +1,47 @@
+# Restrict system calls to specific file
+sp.disable_functions.function("system").filename("update.php").allow();
+sp.disable_functions.function("system").drop();
+
+
+# Restrict system calls to specific file with a specific hash
+sp.disable_functions.function("system").filename("update.php").hash("d27c6c5686bc129716b6aac8dfefe2d519a80eb6cc144e97ad42c728d423eed0").allow();
+sp.disable_functions.function("system").drop();
+
+
+# AbanteCart 1.2.8 - Multiple SQL Injections
+sp.disable_functions.filename("static_pages/index.php").var("_SERVER[PHP_SELF").value_r("\"").drop().alias("XSS");
+sp.disable_functions.filename("core/lib/language_manager.php").function("ALanguageManager>_clone_language_rows").param("from_language").value_r("[^0-9]").drop();
+sp.disable_functions.filename("admin/model/tool/backup.php").function("ModelToolBackup>createBackupTask").param("data[table_list]").value_r("'").drop();
+
+
+# Redaxo 5.2.0: Remote Code Execution via CSRF
+# See for details
+sp.disable_functions.filename("redaxo/src/addons/structure/pages/linkmap.php").function("substr").param("string").value_r("\"").drop();
+
+
+# Guest Post: Vtiger 6.5.0 - SQL Injection
+sp.disable_functions.filename("modules/Calendar/Activity.php").function("save_module").param("query").value_r("[^0-9;]").drop();
+
+
+# The State of Wordpress Security
+# All In One WP Security & Firewall
+sp.disable_functions.filename("admin/wp-security-dashboard-menu.php").function("render_tab3").var("_REQUEST[tab]]").value_r("\"").drop();
+
+
+# PHPKit 1.6.6: Code Execution for Privileged Users
+sp.disable_functions.filename("pkinc/func/default.php").function("move_uploaded_file").param("destination").value_r("\\.ph\\.+$").drop();
+
+
+# Coppermine 1.5.42: Second-Order Command Execution
+sp.disable_functions.filename("include/imageobject_im.class.php").function("exec").var("CONFIG[im_options]).value_r("[^a-z0-9]").drop();
+sp.disable_functions.filename("forgot_passwd.php").function("cpg_db_query").var("CLEAN[id]").value_r("[^a-z0-9]").drop();
+
+
+# CVE-2014-1610 - Mediawiki RCE
+sp.disable_functions.filename("includes/media/DjVu.php")
+sp.disable_functions.filename("includes/media/ImageHandler.php").var("_GET[page]").value_r("[^0-9]").drop()
+
+
+# CVE-2017-1001000 - https://blog.sucuri.net/2017/02/content-injection-vulnerability-wordpress-rest-api.html
+sp.disable_functions.filename("wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php").function("register_routes").var("_GET[id]").value_r("[^0-9]").drop();
+sp.disable_functions.filename("wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php").function("register_routes").var("_POST[id]").value_r("[^0-9]").drop();
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 00000000..6d301b8b
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+snuffleupagus (0.1) UNRELEASED; urgency=medium
+
+ * Initial release.
+
+ -- jvoisin Tue, 04 Jul 2017 17:51:31 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 00000000..f599e28b
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+10
diff --git a/debian/control b/debian/control
new file mode 100644
index 00000000..c1cfb929
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,14 @@
+Source: snuffleupagus
+Priority: optional
+Maintainer: NBS System
+Build-Depends: debhelper (>= 9), php7.0-dev
+Standards-Version: 3.9.2
+Homepage: https://snuffleupagus.fr
+Section: php
+Vcs-Git: https://github.com/nbs-system/snuffleupagus
+
+Package: snuffleupagus
+Architecture: any
+Depends: ${misc:Depends}
+Description: Hardening for your php7 stack
+ Snuffleupagus is cool
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 00000000..a7924524
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,8 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: Snuffleupagus
+Upstream-Contact: NBS System
+Source: https://github.com/nbs-system/snuffleupagus
+
+Files: *
+Copyright: 2017 NBS System
+License: LGPL
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 00000000..b43bf86b
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1 @@
+README.md
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 00000000..66bf44a0
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,27 @@
+#!/usr/bin/make -f
+
+DH_VERBOSE = 1
+
+DPKG_EXPORT_BUILDFLAGS = 1
+include /usr/share/dpkg/default.mk
+
+export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+
+%:
+ dh $@
+
+override_dh_auto_clean:
+ cd ./src; phpize --clean
+
+override_dh_auto_configure:
+ cd ./src; phpize
+ cd ./src; ./configure --enable-snuffleupagus
+
+override_dh_auto_build:
+ make -C src
+
+override_dh_auto_install:
+ make -C src install
+
+override_dh_auto_test:
+ TEST_PHP_ARGS="-q" REPORT_EXIST_STATUS=1 make -C src test
diff --git a/debian/watch b/debian/watch
new file mode 100644
index 00000000..86028c70
--- /dev/null
+++ b/debian/watch
@@ -0,0 +1,2 @@
+version=3
+https://github.com/nbs-system/snuffleupagus/tags /nbs-system/snuffleupagus/archive/snuffleupagus-([0-9.]+)\.tar\.(gz|xz|bz2)
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 00000000..73550116
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SPHINXPROJ = Snuffleupagus
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
diff --git a/doc/source/_static/custom.css b/doc/source/_static/custom.css
new file mode 100644
index 00000000..1c47d049
--- /dev/null
+++ b/doc/source/_static/custom.css
@@ -0,0 +1,4 @@
+blockquote {
+ border-left: 2px solid #999;
+ padding-left: 20px;
+}
\ No newline at end of file
diff --git a/doc/source/_static/sp.jpg b/doc/source/_static/sp.jpg
new file mode 100644
index 00000000..0575ca70
Binary files /dev/null and b/doc/source/_static/sp.jpg differ
diff --git a/doc/source/conf.py b/doc/source/conf.py
new file mode 100644
index 00000000..b2af5f2a
--- /dev/null
+++ b/doc/source/conf.py
@@ -0,0 +1,178 @@
+# -*- coding: utf-8 -*-
+#
+# Snuffleupagus documentation build configuration file, created by
+# sphinx-quickstart on Tue Jun 27 14:15:46 2017.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+from datetime import datetime
+
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+#extensions = ['sphinx.ext.githubpages']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Snuffleupagus'
+copyright = u'%d, NBS System' % datetime.now().year
+author = u'Sebastien Blot & Julien Voisin'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = u'0.1'
+# The full version, including alpha/beta/rc tags.
+release = u'Public Alpha'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = []
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'manni'
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'alabaster'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+
+html_sidebars = {
+ '**': [
+ 'about.html',
+ 'navigation.html',
+ 'relations.html',
+ 'searchbox.html',
+ #'donate.html',
+ ]
+}
+html_theme_options = {
+ 'logo': './sp.jpg',
+ #'description': '
Killing bug-classes in PHP 7, virtual-patching the rest.',
+ #'fixed_sidebar': True,
+ 'page_width': '60%',
+ 'show_powered_by': False,
+ }
+
+sidebar_collapse = True
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+#html_logo = './sp.png'
+
+html_show_sphinx = False
+
+# -- Options for HTMLHelp output ------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Snuffleupagusdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'Snuffleupagus.tex', u'Snuffleupagus Documentation',
+ u'Sebastien Blot \\& Julien Voisin', 'manual'),
+]
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (master_doc, 'snuffleupagus', u'Snuffleupagus Documentation',
+ [author], 1)
+]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'Snuffleupagus', u'Snuffleupagus Documentation',
+ author, 'Snuffleupagus', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+
+
diff --git a/doc/source/config.rst b/doc/source/config.rst
new file mode 100644
index 00000000..8318e7dd
--- /dev/null
+++ b/doc/source/config.rst
@@ -0,0 +1,283 @@
+Configuration
+=============
+
+Since PHP *ini-like* configuration model isn't flexible enough,
+Snuffleupagus is using its own format.
+
+Options are chainable by using dots (``.``), and string parameters
+**must** be quoted, while booleans and integers aren't.
+
+Comments are prefixed either with ``#``, or ``;``.
+
+Some rules applies in a specific ``function`` (context), on a specific ``variable``
+(data), like ``disable_functions``, others can only be enabled/disabled, like
+``harden_random``.
+
+.. warning::
+
+ Careful, a wrongly configured Snuffleupagus might break your website.
+ It's up to you to understand its :doc:`features `,
+ read the present documentation about how to configure them,
+ evaluate your threat model, and write your configuration file accordingly.
+
+The rules are evaluated in the order that they are written, and the **first** one
+to match will terminate the evaluation (except for rules in simulation mode).
+
+Bugclass-killer features
+------------------------
+
+global_strict
+^^^^^^^^^^^^^
+`default: disabled`
+
+``global_strict`` will enable the `strict `_ mode globally,
+forcing PHP to throw a `TypeError `_
+exception if an argument type being passed to a function does not match its corresponding declared parameter type.
+
+It can either be ``enabled`` or ``disabled``
+
+::
+
+ sp.global_strict.disable();
+ sp.global_strict.enable();
+
+harden_random
+^^^^^^^^^^^^^
+ * `default: enabled`
+ * `more `__
+
+``harden_random`` will silently replace the insecure `rand `_
+and `mt_rand `_ functions with
+the secure PRNG `random_int `_.
+
+It can either be ``enabled`` or ``disabled``.
+
+::
+
+ sp.harden_random.enable();
+ sp.harden_random.disable();
+
+.. _config_global:
+
+global
+^^^^^^
+
+This configuration variable contain parameters that are used by other ones:
+
+- ``secret_key``: A secret key used by various cryptographic features,
+ like `cookies protection `__ or `unserialize protection `__,
+ so do make sure that it's random and long enough.
+ You can generate it with something like this: ``head -c 256 /dev/urandom | tr -dc 'a-zA-Z0-9'``.
+
+::
+
+ sp.global.secret_key("44239bd400aa82e125337c9d4eb8315767411ccd");
+
+unserialize_hmac
+^^^^^^^^^^^^^^^^
+ * `default: disabled`
+ * `more `__
+
+``unserialize_hmac`` will add integrity check to ``unserialize`` calls, preventing
+abritrary code execution in their context.
+
+::
+
+ sp.unserialize_hmac.enable();
+ sp.unserialize_hmac.disable();
+
+
+auto_cookie_secure
+^^^^^^^^^^^^^^^^^^
+ * `default: disabled`
+ * `more `__
+
+``auto_cookie_secure`` will automatically mark cookies as `secure `_
+when the web page is requested over HTTPS.
+
+It can either be ``enabled`` or ``disabled``.
+
+::
+
+ sp.auto_cookie_secure.enable();
+ sp.auto_cookie_secure.disable();
+
+cookie_encryption
+^^^^^^^^^^^^^^^^^
+ * `default: disabled`
+ * `more `__
+
+.. warning::
+
+ To use this feature, you **must** set the :ref:`global.secret_key ` variable.
+ This design decision prevents attacker from
+ `trivially bruteforcing `_
+ session cookies.
+
+``cookie_secure`` will activate transparent encryption of specific cookies.
+
+It can either be ``enabled`` or ``disabled``.
+
+::
+
+ sp.cookie_encryption.cookie("my_cookie_name");
+ sp.cookie_encryption.cookie("another_cookie_name");
+
+
+readonly_exec
+^^^^^^^^^^^^^
+ * `default: disabled`
+
+``readonly_exec`` will prevent the execution of writable PHP files.
+
+It can either be ``enabled`` or ``disabled``.
+
+::
+
+ sp.readonly_exec.enable();
+
+upload_validation
+^^^^^^^^^^^^^^^^^
+ * `default: disabled`
+ * `more `__
+
+``upload_validation`` will call a given script upon a file upload, with the path
+to the file being uploaded as argument, and various information about it in the environment:
+
+* ``SP_FILENAME``: the name of the uploaded file
+* ``SP_FILESIZE``: the size of the file being uploaded
+* ``SP_REMOTE_ADDR``: the ip address of the uploader
+* ``SP_CURRENT_FILE``: the current file being executed
+
+This feature can be used, for example, to check if an uploaded file contains php
+code, with something like `vld `_
+(``php -d vld.execute=0 -d vld.active=1 -d extension=vld.so yourfile.php``).
+
+The upload will be **allowed** if the script return the value ``0``. Every other
+value will prevent the file from being uploaded.
+
+::
+
+ sp.upload_validation.script("/var/www/is_valid_php.py").enable();
+
+
+disable_xxe
+^^^^^^^^^^^
+ * `default: enabled`
+ * `more `__
+
+``disable_xxe`` will prevent XXE attacks by disabling the loading of external entities (``libxml_disable_entity_loader``) in the XML parser.
+
+::
+
+ sp.disable_xxe.enable();
+
+
+Virtual-patching
+----------------
+
+Snuffleupagus provides virtual-patching, via the ``disable_functions`` directive, allowing you to stop or control dangerous behaviours.
+Admitting you have a call to ``system()`` that lacks proper user-input validation, thus leading to an **RCE**, this might be the right tool.
+
+::
+
+ # Allow `id.php` to restrict system() calls to `id`
+ sp.disable_functions.function("system").filename("id.php").param("cmd").value("id").allow();
+ sp.disable_functions.function("system").filename("id.php").drop()
+
+Of course, this is a trivial example, and a lot can be achieved with this feature, as you will see below.
+
+
+Filters
+^^^^^^^
+
+- ``alias(:str)``: human-readable description of the rule
+- ``cidr(ip/mask:str)``: match on the client's `cidr `_
+- ``filename(name:str)``: match in the file ``name``
+- ``filename_r(regexp:str)``: the file name matching the ``regexp``
+- ``function(name:str)``: match on function ``name``
+- ``function_r(regexp:str)``: the function matching the ``regexp``
+- ``hash(:str)``: match on the file's `sha256 `_ sum
+- ``param(name:str)``: match on the function's parameter ``name``
+- ``param_r(regexp:str)``: match on the function's parameter ``regexp``
+- ``param_type(type:str)``: match on the function's parameter ``type``
+- ``ret(value:str)``: match on the function's return ``value``
+- ``ret_r(regexp:str)``: match with a ``regexp`` on the function's return
+- ``ret_type(type_name:str)``: match on the ``type_name`` of the function's return value
+- ``value(:str)``: match on a litteral value
+- ``value_r(:regexp)``: match on a value matching the ``regexp``
+- ``var(name:str)``: match on a **local variable** ``name``
+
+The ``type`` must be one of the following values:
+
+- ``FALSE``: for boolean false
+- ``TRUE``: for boolean true
+- ``NULL``: for the **null** value
+- ``LONG``: for a long (also know as ``integer``) value
+- ``DOUBLE``: for a **double** (also known as ``float``) value
+- ``STRING``: for a string
+- ``OBJECT``: for a object
+- ``ARRAY``: for an array
+- ``RESOURCE``: for a resource
+
+Actions
+^^^^^^^
+
+- ``allow()``: **allow** the request if the rule matches
+- ``drop()``: **drop** the request if the rule matches
+- ``dump(directory:str)``: dump the request in the ``directory`` if it matches the rule
+- ``simulation()``: enabled the simulation mode
+
+Details
+^^^^^^^
+
+The ``function`` filter is able to do various dereferencing:
+
+- ``function("AwesomeClass::my_method")`` will match in the method ``my_method`` in the class ``AwesomeClass``
+
+The ``param`` filter is also able to do some dereferencing:
+
+- ``param(foo[bar])`` will get match on the value corresponding to the ``bar`` key in the hashtable ``foo``.
+ Remember that in PHP, almost every data structure is a hashtable. You can of course nest this like
+ ``param(foo[bar][baz][batman])``.
+- The ``var`` filter will walk the calltrace until it finds the variable's name, or the end of it,
+ allowing to match on global variables: ``.var("_GET[param]")`` will match on the GET parameter ``param``.
+
+For clarity's sake, the presence of the ``allow`` or ``drop`` action is **mandatory**.
+
+.. warning::
+
+ When you're writing rules, please do keep in mind that the **order matters**.
+ For example, if you're denying a call to ``system()`` and then allowing it in a
+ more narrowed way later, the call will be denied,
+ because it'll match the deny first.
+
+If you're paranoid, we're providing a php script to automatically generate
+hash of files containing dangerous functions,
+and blacklisting them everywhere else.
+
+Examples
+^^^^^^^^
+
+Evaluation order of rules
+"""""""""""""""""""""""""
+
+The following rules will:
+
+1. Allow calls to ``system("id")``
+2. Issue a trace in the logs on calls to ``system`` with its parameters starting with ``ping``,
+ and pursuing evaluation of the remaining rules.
+3. Drop calls to ``system``.
+
+
+::
+
+ sp.disable_functions.function("system").param("cmd").value("id").allow();
+ sp.disable_functions.function("system").param("cmd").value_r("^ping").drop().simulation();
+ sp.disable_functions.function("system").param("cmd").drop();
+
+Miscellaneous examples
+""""""""""""""""""""""
+
+.. literalinclude:: ../../config/examples.ini
+ :language: python
\ No newline at end of file
diff --git a/doc/source/download.rst b/doc/source/download.rst
new file mode 100644
index 00000000..161937f1
--- /dev/null
+++ b/doc/source/download.rst
@@ -0,0 +1,16 @@
+Download
+========
+
+Debian
+------
+
+
+Ubuntu
+------
+
+
+Source code
+-----------
+
+::
+ git clone https://github.com/nbs-system.com/snuffleupagus
diff --git a/doc/source/faq.rst b/doc/source/faq.rst
new file mode 100644
index 00000000..07aba330
--- /dev/null
+++ b/doc/source/faq.rst
@@ -0,0 +1,196 @@
+FAQ
+===
+
+General
+-------
+
+What is Snuffleupagus?
+""""""""""""""""""""""
+
+Snuffleupagus is a `PHP7+ `_
+module designed to drastically raising the cost of attacks against website,
+by killing entire bug classes, and also providing a powerful virtual-patching system,
+allowing administrator to fix specific vulnerabilities without having to touch the PHP code.
+
+
+Where does the name *Snuffeupagus* comes from?
+""""""""""""""""""""""""""""""""""""""""""""""
+
+ Aloysius Snuffleupagus, more commonly known as Mr. Snuffleupagus, Snuffleupagus
+ or Snuffy for short, is one of the characters on Sesame Street,
+ the educational television program for young children.
+
+ He was created as a woolly mammoth, without tusks or (visible) ears,
+ and has a long thick pointed tail, similar in shape to that of a dinosaur
+ or other reptile. He has long thick brown hair and a trunk, or "snuffle",
+ that drags along the ground. He is Big Bird's best friend and
+ has a baby sister named Alice. He also attends "Snufflegarten".
+
+ --- `Wikipedia `_
+
+
+Why is Snuffleupagus called Snuffleupagus?
+""""""""""""""""""""""""""""""""""""""""""
+
+Like PHP's `ElePHPant `_,
+we thought that using an elephant as a mascot would be a great idea.
+
+
+Why did you write Snuffleupagus?
+""""""""""""""""""""""""""""""""
+
+We're working for `NBS System `__,
+a web hosting company (meaning that we're dealing with PHP code all day long),
+with a strong focus on security. We do have hardening
+(kernel, `WAF `_, `IDS `_, …)
+below the web stack, but most of the time, when a website is compromised,
+it's either to send ads, spam, deface it, steal data, …
+This is why we need to harden the website itself too, but we can't touch its
+source code.
+
+Why not Suhosin?
+""""""""""""""""
+
+We're huge fans of `Suhosin `_, unfortunately:
+
+- it doesn't work very well on PHP 7
+- it has some oudated features and misses new ones
+- it doesn't cope very well with our various industrialization needs
+- it has some shortcomings by design
+
+We're using the `disable_function `_
+directive, but unfortunately, it doesn't provide enough usable granularity (guess how many CMS are using
+``system`` to do various mandatory maintenance tasks…).
+
+This is why we decided to write our own hardening module, in the spirit of Suhosin,
+via virtual-patching support, and other cool new features.
+
+What license is Snuffleupagus under and why?
+""""""""""""""""""""""""""""""""""""""""""""
+
+Snuffleupagus is licensed under the `LGPL `_,
+and is developed by the fine people from `NBS System `__.
+
+We chose the LGPL because we don't care that much how you're using Snuffleupagus,
+but we'd like to force people to make their improvements/contributions
+available to everyone.
+
+Should I use Snuffleupagus?
+"""""""""""""""""""""""""""
+
+Yes.
+
+Even if you're not using the virtual-patching capabilities, Snuffleupagus comes
+with various passive features that won't break your website while killing numerous vulnerabilities.
+
+Please keep in mind that you are not only protecting yourself and your users/customers,
+but also other people on the internet that might be attacked by your server if
+it becomes compromised.
+
+How mature is this project?
+"""""""""""""""""""""""""""
+
+This project was floating around since early 2016, and we did the first commit
+the 28ᵗʰ of December of the same year. We're currently in a private alpha phase,
+finding and fixing as much bugs as possible with the help of friends.
+
+Are you saying that PHP isn't secure?
+"""""""""""""""""""""""""""""""""""""
+
+We don't like PHP's approach of security; namely (sometimes) adding warnings
+in the documentation and trusting the developer to not do any mistake,
+instead of focusing on the root cause, and killing the
+bug class one for all.
+
+Moreover, it seems that the current attitude toward security in the PHP world
+is to `blame the user `_ instead of acknowledging
+issues, as stated in their `documentation `_.
+We do think that an security issue that "requires the use of code or settings known to be insecure"
+is still a security issue, and should be treated as such.
+
+Installation and configuration
+------------------------------
+
+Can snuffleupagus break my application?
+"""""""""""""""""""""""""""""""""""""""
+Yes.
+
+Some options won't break anything, like ``harden_rand``, but some like ``global_strict``
+or overly-restrictives virtual-patching rules might pretty well break your website.
+It's up to you to configure Snuffleupaggus accordingly to your needs.
+
+You can also enable the ``simulation`` mode on features that you're not sure about,
+to see what would snuffleupagus do to your application, before activating them for good.
+
+How can I find out the problem when my application breaks?
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+
+By checking the logs; Snuffleupagus systematically prefix them with ``[snuffleupagus]``.
+
+
+Does Snuffleupagus run on Windows?
+""""""""""""""""""""""""""""""""""
+No idea.
+
+
+Will Snuffleupagus run on my old PHP 5?
+"""""""""""""""""""""""""""""""""""""""
+No.
+
+Since PHP5 `will be deprecated at the end of 2018 `_,
+you should think about moving to PHP7 anyway. You can (and should) use
+`Suhosin `_ in the meantime.
+
+Help and support
+----------------
+
+I found a security issue
+""""""""""""""""""""""""
+If you believe you have found a security issue affecting Snuffleupagus,
+then we would be more than happy to hear from you!
+
+We promise to treat any reported issue seriously and,
+if the investigation confirms it affects Snuffleupagus,
+to patch it within a reasonable time,
+release a public announcement that describes the issue,
+discuss potential impact of the vulnerability,
+reference applicable patches or workarounds,
+and credit the discoverer.
+
+Please send it us a mail to the ``snuffleupagus`` user,
+on ``nbs-system.com``.
+
+I found a bug. How can I report it?
+"""""""""""""""""""""""""""""""""""
+We do have an issue tracker on `Github `_.
+Please make sure to include as much information as possible when reporting your issue,
+such as your operating system, your version of PHP 7, your version of snuffleupagus,
+your logs, the problematic php code, the request, a brief description, … long story short,
+give us everything that you can.
+
+Where can I find even more help?
+""""""""""""""""""""""""""""""""
+The :doc:`configuration page ` might be what you're looking for.
+If you're adventurous, you can also check the `issue tracker `_
+(make sure to check the closed issues too).
+
+I need professional support for my company.
+"""""""""""""""""""""""""""""""""""""""""""
+Contact `NBS System `_.
+
+Unimplemented mitigations and abandoned ideas
+---------------------------------------------
+
+Contant time comparisons
+""""""""""""""""""""""""
+We didn't manage to perform time-based side-channel attacks on strings
+against real world PHP application, and the results that we gathered on
+tailored test cases weren't concluding: for simplicity's sake, we chose
+to not implement a mitigation against this class of attacks.
+
+We would be happy to be proven wrong, and reconsider implementing this feature,
+if someone can manage to get better results than us.
+
+The possibility of having this natively in PHP has
+`been discussed `_,
+but as 2017, nothing has been merged yet.
diff --git a/doc/source/features.rst b/doc/source/features.rst
new file mode 100644
index 00000000..89cd756d
--- /dev/null
+++ b/doc/source/features.rst
@@ -0,0 +1,352 @@
+Features
+========
+
+Snuffleupagus has a lot of features that can be divided in two main categories: bug-classes
+killers and virtual-patching. The first category provides primitives to kill various
+bug families (like arbitrary code execution via ``unserialize`` for example) or rise the
+cost of exploitation, the second one is a highly configurable system to patch functions in php itself.
+
+Bug classes killed
+------------------
+
+``system`` injections
+^^^^^^^^^^^^^^^^^^^^^
+
+The ``system`` function execute an external program and displays the output.
+It's used to interract with various external tools, like file-format converters for example.
+Unfortunately, passing user-controlled parameters to it often leads to an arbitrary command execution.
+
+ When allowing user-supplied data to be passed to this function,
+ use `escapeshellarg()` or `escapeshellcmd()` to ensure that users cannot trick
+ the system into executing arbitrary commands.
+
+ --- `The PHP documentation about system `_
+
+We're kind of killing it by filtering the ``$``, ``|``, ``;``, ````` and ``&`` chars in our
+default configuration, making it a lot harder for an attacker to inject arbitrary commands.
+
+This family of vulnerabilities lead to various CVE, like:
+
+- `CVE-2017-7981 `_: Authenticated remote code execution on Tuleap
+- `CVE-2014-4688 `_: Authenticated remote code execution on pfSense
+- `CVE-2014-1610 `_: Unauthenticated remote code execution on DokuWiki
+- `CVE-2013-3630 `_: Authenticated remote code execution on Moodle
+- Every single shitty `modem/router/switch/IoT `_.
+
+
+``mail``-related injections
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This vulnerability is known `since 2011 `_,
+and was popularized by `RIPS `_ in 2016.
+The last flag of the `mail` function can be used to pass various parameters to
+the underlying binary used to send emails: this can lead to an arbitrary file write,
+often meaning an arbitrary code execution.
+
+ The ``additional_parameters`` parameter can be used to pass additional flags
+ as command line options to the program configured to be used when sending mail
+
+ --- `The PHP documentation about mail `_
+
+We're killing it by preventing any extra options in additional_parameters.
+
+This family of vulnerabilities lead to various CVE, like:
+
+- `CVE-2017-7692 `_: Authenticated remote code execution in SquirrelMail
+- `CVE-2016-10074 `_: remote code execution in SwiftMailer
+- `CVE-2016-10033 `_: remote code execution in PHPMailer
+- `CVE-2016-9920 `_: Unauthenticated remote code execution in Roundcube
+
+Session-cookie stealing via XSS
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The goto payload for XSS is often to steal cookies.
+Like *Suhosin*, we are encrypting the cookies with a secret key, the IP of the user
+and its user-agent. This means that an attacker with an XSS won't be able to use
+the stolen cookie, since he (often) can't spoof the IP address of the user.
+
+This feature is roughly the same than the `Suhosin one `_.
+
+Users behind the same IP address but with different browsers won't be able to use each other stolen cookies,
+except if they can manage to guess the user agent. This isn't especially difficult,
+but an invalid decryption will leave a trace in the logs.
+
+Finally, having a secret server-side key will prevent anyone (even the user himself)
+from reading the content of the cookie, reducing the impact of an application storing sensitive data client-side.
+
+The encryption is done via the [tweetnacl library](https://tweetnacl.cr.yp.to/),
+thus using curve25519, xsalsa20 and poly1305 for the encryption. We chose this
+library because of its portability, simplicity and reduced size (a single `.h` and
+`.c` file.).
+
+Remote code execution via file-upload
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Some PHP applications allows users to upload contents, like avatars for a forum.
+Unfortunately, sometimes, content validation isn't implemented properly (if at all),
+meaning arbitrary file upload, often leading, contrary to what the documentation is saying,
+to an arbitrary code execution.
+
+ Not validating which file you operate on may mean that users can *access sensitive information* in other directories.
+
+ --- `The PHP documentation about file uploads `_
+
+We're killing it, like Suhosin, by automatically calling a script upon file upload,
+if it returns something else than ``0``, the file will be removed (or stored in a quarantine,
+for further analysis).
+
+We're recommending to use the `vld `_ project
+inside the script to ensure the file doesn't contain any valid PHP code, with something like this:
+
+::
+
+ $ php -d vld.execute=0 -d vld.active=1 -d extension=vld.so $file
+
+Unserialize-related magic
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+PHP is able to *serialize* arbitrary objects, to easily store them.
+Unfortunately, it's often possible to gain arbitrary code execution upon deserialization
+of user-supplied serialized objects.
+
+ Do not pass untrusted user input to ``unserialize()`` regardless of the options value of allowed_classes.
+ Unserialization can result in code being loaded and executed due to object instantiation and autoloading,
+ and a malicious user may be able to exploit this.
+
+ --- `The PHP documentation about serialize `_
+
+We're killing it by exploiting the fact that PHP will discard any garbage found at the end of a serialized object,
+allowing us to simply append a `HMAC `_
+at the end of strings generated by the ``serialize``,
+hence guaranteeing that any object deserialized came from the application,
+and wasn't tampered with,
+
+We're not encrypting it, like we do with the cookies,
+allowing this feature to be disabled (or switch into leaning mode)
+without the need to invalidate any data.
+
+.. warning::
+
+ This feature can't be deployed on websites that already stored serialized
+ objects (ie. in database), since they are missing the HMAC, and thus will be detected as
+ an attack. If you're in this situation, you should use this feature with the
+ ``simulation`` mode, and switch it off once you don't have any messages in your
+ logs.
+
+A nice side-effect of this feature is that it'll defeat various memory corruption
+issues related to the complexity of ``unserialize``'s implementation,
+and the amount of control if provides to an attacker, like `CVE-2016-9137, CVE-2016-9138 `_,
+`2016-7124 `_, `CVE-2016-5771 and CVE-2016-5773 `_, …
+
+This family of vulnerabilities lead to various CVE, like:
+
+- `CVE-2016-???? `_: Unauthenticated remote code execution in Observium (leading to remote root)
+- `CVE-2016-5726 `_: Unauthenticated remote code execution in Simple Machines Forums
+- `CVE-2016-4010 `_: Unauthenticated remote code execution in Magento
+- `CVE-2017-2641 `_: Unauthenticated remote code execution in Moodle
+- `CVE-2015-8562 `_: Unauthenticated remote code execution in Joomla
+- `CVE-2015-7808 `_: Unauthenticated remote code execution in vBulletin
+- `CVE-2014-1691 `_: Unauthenticated remote code execution in Horde
+- `CVE-2012-5692 `_: Unauthenticated remote code execution in IP.Board
+
+
+
+Weak-PRNG via rand/mt_rand
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The functions ``rand`` and ``mt_rand`` are often used to generate random numbers used
+in sensitive context, like password generation, token creation, …
+Unfortunately, as said in the documentation, the quality of their entropy is low,
+leading to the generation of guessable values.
+
+ This function does not generate cryptographically secure values, and should not be used for cryptographic purposes.
+
+ --- `The PHP documentation about rand `_
+
+We're addressing this issue by replacing every call to ``rand`` and ``mt_rand`` with
+a call to the ``random_int``, a `CSPRNG `_.
+
+It's worth noting that the PHP documentation contains the following warning:
+
+ ``min`` ``max`` range must be within the range ``getrandmax()``. i.e. ``(max - min) <= getrandmax()``.
+ Otherwise, ``rand()`` may return poor-quality random numbers.
+
+ --- `The PHP documentation about rand `_
+
+This is of course addressed as well by the ``harden_rand`` feature.
+
+.. warning::
+
+ Activating this feature will raise an `Error `_
+ exception if ``min`` is superior to ``max``, while the default dehaviour is simply to swap them.
+
+This family of vulnerabilities lead to various CVE, like:
+
+- `CVE-2015-5267 `_: Unauthenticated accounts takeover in in Moodle
+- `CVE-2014-9624 `_: Captcha bypass in MantisBT
+- `CVE-2014-6412 `_: Unauthenticated account takeover in Wordpress
+- `CVE-2015-???? `_: Unauthenticated accounts takeover in Concrete5
+- `CVE-2013-6386 `_: Unauthenticated accounts takeover in Drupal
+- `CVE-2010-???? `_: Unauthenticated accounts takeover in MyBB
+- `CVE-2008-4102 `_: Unauthenticated accounts takeover in Joomla
+- `CVE-2006-0632 `_: Unauthenticated account takeover in phpBB
+
+XXE
+^^^
+
+Despite the documentation saying nothing about this class of vulnerabilities,
+`XML eXternal Entitiy `_ (XXE) are often leading to arbitrary file reading, SSRF, and sometimes even arbitrary
+code execution.
+
+XML documents can contain a `Document Type Definition `_ (DTD),
+enabling definition of XML entities. It's possible to define an (external) entity by an
+URI, that the parser will access, and embed its content back into the document
+for further processing.
+
+For example, providing an url like ``file:///etc/passwd`` will read
+this file's content, and since it's not valid XML, the application
+will spit it out in an error message, thus leaking its content.
+
+We're killing this class of vulnerabilities by calling
+the `libxml_disable_entity_loader `_
+function with its parameter set to ``true`` at startup,
+and then *nop'ing* it, so it won't do anything if ever called again.
+
+This family of vulnerabilities lead to various CVE, like:
+
+- `CVE-2015-5161 `_: Unauthenticated arbitrary file disclosure on Magento
+- `CVE-2014-8790 `_: Unauthenticated remote code execution in GetSimple CMS
+- `CVE-2011-4107 `_: Authenticated local file disclosure in PHPMyAdmin
+
+
+Cookie stealing via HTTP MITM
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+While it's possible to set the ``secure`` flag on cookies to prevent them from being
+transmitted over HTTP, and only allow its transmission over HTTPS.
+Snuffleupagus can automatically set this flag if the client is accessing the
+website over a secure connection.
+
+This behaviour is suggested in the documentation:
+
+ On the server-side, it's on the programmer to send this kind of cookie only
+ on secure connection (e.g. with respect to ``$_SERVER["HTTPS"]``).
+
+ --- `The PHP documentation about setcookie `_
+
+
+Exploitation, post-exploitation and general hardening
+-----------------------------------------------------
+
+Virtual-patching
+^^^^^^^^^^^^^^^^
+
+PHP itself exposes a number of functions that might be considered **dangerous** and that have limited legitimate use cases.
+``system()``, ``exec()``, ``dlopen()`` - for example - fall into this category. By default, PHP only allows to globally disable some functions.
+
+
+However, (ie. ``system()``) they might have legitimate use cases in processes such as self upgrade etc., making it impossible to effectively
+disable them - at the risk of breaking critical features.
+
+SnuffleuPagus allows the user to restrict usage of specific functions per files, or per
+files with a matching (sha256) hash, thus allowing the use of such functions **only** in the intended places.
+
+Furthermore, running the `following script `_ will generate an hash and line-based whitelist
+of dangerous functions, droping them everywhere else:
+
+
+.. literalinclude:: ../../scripts/generate_rules.php
+ :language: php
+
+
+The intent is to make post-exploitation process (such as backdooring of legitimate code, or RAT usage) a lot harder for the attacker.
+
+
+Global strict mode
+^^^^^^^^^^^^^^^^^^
+
+By default, PHP will coerce values of the wrong type into the expected one
+if possible. For example, if a function expecting an integer is given a string,
+it will be coerced in an integer.
+
+PHP7 introduced a **strict mode**, in which variables won't be coerced anymore,
+and a `TypeError `_ exception will
+be raised if the types aren't matching.
+`Scalar type declarations `_
+are optional, but you don't have to used them in your code to benefit from them,
+since every internal function from php has them.
+
+This option provide a switch to globally activate this strict mode,
+helping to uncover vulnerabilities like the classical
+`strcmp bypass `_,
+and various other types mismatch.
+
+This feature is largely inspired from the
+`autostrict `_ module from `krakjoe `_.
+
+
+Preventing execution of writable PHP files
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If an attacker manages to upload an arbitrary file or to modify an existing one,
+odds are that (thanks to the default `umask `_)
+this file is writable by the PHP process.
+
+Snuffleupagus can prevent the execution of this kind of files. A good practise
+would be to use a different user to run PHP than for administrating the website,
+and using this feature to lock this up.
+
+
+
+Dumping capabilities
+^^^^^^^^^^^^^^^^^^^^
+It's possible to apply the ``dump(:str)`` filter to any virtual-patching rule,
+to dump the complete web request, along with the filename and the corresponding
+line number. By using the *right* set of restrictive rules (or by using the
+*overly* restrictives ones in ``simulation`` mode), you might be able
+to gather interesting vulnerabilities used against your website.
+
+
+Misc low-hanging fruits in the default configuration file
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Snuffleupagus is shipping with a default configuration file, containing
+various examples and ideas of things that you might want to enable (or not).
+
+Available functions recon
+"""""""""""""""""""""""""
+
+After compromising a website, most of the time, the attacker does some recon
+within its webshell, to check which functions are available to execute arbitrary code,
+since it's not uncommon for some web-hoster to disable things like ``system`` or ``passthru``,
+or to check if mitigations are enabled, like ``open_basedir``.
+This behaviour can be detected by preventing the execution of functions like ``ini_get``
+or ``is_callable`` with *suspicious* parameters.
+
+``chmod`` hardening
+"""""""""""""""""""
+
+Some PHP applications are using broad rights when using the ``chmod`` function,
+like the infamous ``chmod(777)`` command, effectively making the file writable by everyone.
+Snuffleupagus is preventing this kind of behaviour by restricting the parameters
+than can be passer to ``chmod``.
+
+Arbitrary file inclusion hardening
+""""""""""""""""""""""""""""""""""
+
+Arbitrary file inclusion is a common vulnerability, that might be detected
+by preventing the use of anything else than a whitelist of extensions in calls
+to ``include`` or ``require``.
+
+*Cheap* SQL injections detection
+""""""""""""""""""""""""""""""""
+
+In some SQL injections, attackers might need to use comments, a feature that is
+often not used in production system, so it might be a good idea to filter
+queries that contains some. The same filtering idea can be used against
+SQL functions that are frequently used in SQL injections, like ``sleep``, ``benchmark``
+or strings like ``version_info``.
+
+Still about SQL injections, if a function performing a query returns ``FALSE``
+(indicating an error), it might be useful to dump the request for further analysis.
+
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644
index 00000000..8a9c340b
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,30 @@
+Snuffleupagus
+=============
+
+Snuffleupagus is a `PHP 7+ `_ module designed to drastically raising the cost of
+attacks against website, by killing entire bug classes, and also providing a
+powerful virtual-patching system, allowing administrator to fix specific
+vulnerabilities and audit suspicious behaviours without having to touch the PHP code.
+
+Documentation
+-------------
+
+.. toctree::
+ :maxdepth: 2
+
+ features
+ installation
+ config
+ download
+ faq
+ papers
+
+Greetings
+---------
+We would like to thank the following people:
+
+- `Suhosin `_, for paving the way.
+- The people behind the `RIPS `_ scanner for their ground breaking work
+- `NBS System `_, for creating and open-sourcing this piece of software
+- `Websec.fr `_, for keeping our interesting vulnerabilities alive
+- Web developers around the world, for being so imaginative
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
new file mode 100644
index 00000000..779008d6
--- /dev/null
+++ b/doc/source/installation.rst
@@ -0,0 +1,33 @@
+Installation
+============
+
+Snuffleupagus is tested against various PHP 7+ versions: XXX
+
+Manual installation
+-------------------
+
+Depending on the system, we might already offer binary packages.
+You can check our :doc:`download`. In that case you only need to activate
+the extension inside your ``php.ini`` and to configure it.
+
+
+Quickstart
+^^^^^^^^^^
+
+::
+
+ git clone https://github.com/nbs-system/snuffleupagus
+ cd snuffleupagus
+ phpize
+ ./configure
+ make
+ make install
+
+This should install ``snuffleupagus.so`` file in your extension directory. The final step is adding a load directive to ``php.ini``::
+
+ extension=snuffleupagus.so
+
+Upgrading
+---------
+
+Upgrading the Snuffleupagus is as simple as recompiling it (or using a binary), replacing the file and restarting your webserver.
diff --git a/doc/source/papers.rst b/doc/source/papers.rst
new file mode 100644
index 00000000..d028b141
--- /dev/null
+++ b/doc/source/papers.rst
@@ -0,0 +1,16 @@
+Propaganda
+==========
+
+This pages lists various mentions, articles and presentations about Snuffleupagus.
+
+Talks
+-----
+
+- `BerlinSide0x08 `_ - `Php7 Nightmares `_ - 2017-05-28
+- `Hack.lu `_ - soon™ - 2017-10-18
+- `BlackAlps `_ - soon™ - 2017-11-16
+
+Articles
+--------
+
+- `Killing php bug classes at berlinsides `_ - 2017-06-05
\ No newline at end of file
diff --git a/scripts/generate_rules.php b/scripts/generate_rules.php
new file mode 100644
index 00000000..e286ef12
--- /dev/null
+++ b/scripts/generate_rules.php
@@ -0,0 +1,43 @@
+ $object){
+ if (FALSE === in_array (pathinfo($name, PATHINFO_EXTENSION), $extensions, true)) {
+ continue;
+ }
+
+ $hash = '';
+ $file_content = file_get_contents($name);
+
+ foreach(token_get_all($file_content) as $token) {
+ if ($token[0] != 319) {
+ continue;
+ }
+
+ if (in_array($token[1], $functions_blacklist, true)) {
+ if ('' === $hash) {
+ $hash = hash('sha256', $file_content);
+ }
+ echo 'sp.disable_function.function("' . $token[1] . '").filename("' . $name . '").hash("' . $hash . '").allow();' . "\n";
+ }
+ }
+}
+foreach($functions_blacklist as $fun) {
+ echo 'sp.disable_function.function("' . $fun . '").drop();' . "\n";
+
+}
diff --git a/src/bench/bench.php b/src/bench/bench.php
new file mode 100644
index 00000000..5f771803
--- /dev/null
+++ b/src/bench/bench.php
@@ -0,0 +1,422 @@
+0)) {
+ $im=$re*$im*2+$imc;
+ $re=$re2-$im2+$rec;
+ $re2=$re*$re;
+ $im2=$im*$im;
+ $color=$color-1;
+ }
+ if ( $color==0 ) {
+ print "_";
+ } else {
+ print "#";
+ }
+ }
+ print "
";
+ flush();
+ }
+}
+
+/****/
+
+function mandel2() {
+ $b = " .:,;!/>)|&IH%*#";
+ //float r, i, z, Z, t, c, C;
+ for ($y=30; printf("\n"), $C = $y*0.1 - 1.5, $y--;){
+ for ($x=0; $c = $x*0.04 - 2, $z=0, $Z=0, $x++ < 75;){
+ for ($r=$c, $i=$C, $k=0; $t = $z*$z - $Z*$Z + $r, $Z = 2*$z*$Z + $i, $z=$t, $k<5000; $k++)
+ if ($z*$z + $Z*$Z > 500000) break;
+ echo $b[$k%16];
+ }
+ }
+}
+
+/****/
+
+function Ack($m, $n){
+ if($m == 0) return $n+1;
+ if($n == 0) return Ack($m-1, 1);
+ return Ack($m - 1, Ack($m, ($n - 1)));
+}
+
+function ackermann($n) {
+ $r = Ack(3,$n);
+ print "Ack(3,$n): $r\n";
+}
+
+/****/
+
+function ary($n) {
+ for ($i=0; $i<$n; $i++) {
+ $X[$i] = $i;
+ }
+ for ($i=$n-1; $i>=0; $i--) {
+ $Y[$i] = $X[$i];
+ }
+ $last = $n-1;
+ print "$Y[$last]\n";
+}
+
+/****/
+
+function ary2($n) {
+ for ($i=0; $i<$n;) {
+ $X[$i] = $i; ++$i;
+ $X[$i] = $i; ++$i;
+ $X[$i] = $i; ++$i;
+ $X[$i] = $i; ++$i;
+ $X[$i] = $i; ++$i;
+
+ $X[$i] = $i; ++$i;
+ $X[$i] = $i; ++$i;
+ $X[$i] = $i; ++$i;
+ $X[$i] = $i; ++$i;
+ $X[$i] = $i; ++$i;
+ }
+ for ($i=$n-1; $i>=0;) {
+ $Y[$i] = $X[$i]; --$i;
+ $Y[$i] = $X[$i]; --$i;
+ $Y[$i] = $X[$i]; --$i;
+ $Y[$i] = $X[$i]; --$i;
+ $Y[$i] = $X[$i]; --$i;
+
+ $Y[$i] = $X[$i]; --$i;
+ $Y[$i] = $X[$i]; --$i;
+ $Y[$i] = $X[$i]; --$i;
+ $Y[$i] = $X[$i]; --$i;
+ $Y[$i] = $X[$i]; --$i;
+ }
+ $last = $n-1;
+ print "$Y[$last]\n";
+}
+
+/****/
+
+function ary3($n) {
+ for ($i=0; $i<$n; $i++) {
+ $X[$i] = $i + 1;
+ $Y[$i] = 0;
+ }
+ for ($k=0; $k<1000; $k++) {
+ for ($i=$n-1; $i>=0; $i--) {
+ $Y[$i] += $X[$i];
+ }
+ }
+ $last = $n-1;
+ print "$Y[0] $Y[$last]\n";
+}
+
+/****/
+
+function fibo_r($n){
+ return(($n < 2) ? 1 : fibo_r($n - 2) + fibo_r($n - 1));
+}
+
+function fibo($n) {
+ $r = fibo_r($n);
+ print "$r\n";
+}
+
+/****/
+
+function hash1($n) {
+ for ($i = 1; $i <= $n; $i++) {
+ $X[dechex($i)] = $i;
+ }
+ $c = 0;
+ for ($i = $n; $i > 0; $i--) {
+ if ($X[dechex($i)]) { $c++; }
+ }
+ print "$c\n";
+}
+
+/****/
+
+function hash2($n) {
+ for ($i = 0; $i < $n; $i++) {
+ $hash1["foo_$i"] = $i;
+ $hash2["foo_$i"] = 0;
+ }
+ for ($i = $n; $i > 0; $i--) {
+ foreach($hash1 as $key => $value) $hash2[$key] += $value;
+ }
+ $first = "foo_0";
+ $last = "foo_".($n-1);
+ print "$hash1[$first] $hash1[$last] $hash2[$first] $hash2[$last]\n";
+}
+
+/****/
+
+function gen_random ($n) {
+ global $LAST;
+ return( ($n * ($LAST = ($LAST * IA + IC) % IM)) / IM );
+}
+
+function heapsort_r($n, &$ra) {
+ $l = ($n >> 1) + 1;
+ $ir = $n;
+
+ while (1) {
+ if ($l > 1) {
+ $rra = $ra[--$l];
+ } else {
+ $rra = $ra[$ir];
+ $ra[$ir] = $ra[1];
+ if (--$ir == 1) {
+ $ra[1] = $rra;
+ return;
+ }
+ }
+ $i = $l;
+ $j = $l << 1;
+ while ($j <= $ir) {
+ if (($j < $ir) && ($ra[$j] < $ra[$j+1])) {
+ $j++;
+ }
+ if ($rra < $ra[$j]) {
+ $ra[$i] = $ra[$j];
+ $j += ($i = $j);
+ } else {
+ $j = $ir + 1;
+ }
+ }
+ $ra[$i] = $rra;
+ }
+}
+
+function heapsort($N) {
+ global $LAST;
+
+ define("IM", 139968);
+ define("IA", 3877);
+ define("IC", 29573);
+
+ $LAST = 42;
+ for ($i=1; $i<=$N; $i++) {
+ $ary[$i] = gen_random(1);
+ }
+ heapsort_r($N, $ary);
+ printf("%.10f\n", $ary[$N]);
+}
+
+/****/
+
+function mkmatrix ($rows, $cols) {
+ $count = 1;
+ $mx = array();
+ for ($i=0; $i<$rows; $i++) {
+ for ($j=0; $j<$cols; $j++) {
+ $mx[$i][$j] = $count++;
+ }
+ }
+ return($mx);
+}
+
+function mmult ($rows, $cols, $m1, $m2) {
+ $m3 = array();
+ for ($i=0; $i<$rows; $i++) {
+ for ($j=0; $j<$cols; $j++) {
+ $x = 0;
+ for ($k=0; $k<$cols; $k++) {
+ $x += $m1[$i][$k] * $m2[$k][$j];
+ }
+ $m3[$i][$j] = $x;
+ }
+ }
+ return($m3);
+}
+
+function matrix($n) {
+ $SIZE = 30;
+ $m1 = mkmatrix($SIZE, $SIZE);
+ $m2 = mkmatrix($SIZE, $SIZE);
+ while ($n--) {
+ $mm = mmult($SIZE, $SIZE, $m1, $m2);
+ }
+ print "{$mm[0][0]} {$mm[2][3]} {$mm[3][2]} {$mm[4][4]}\n";
+}
+
+/****/
+
+function nestedloop($n) {
+ $x = 0;
+ for ($a=0; $a<$n; $a++)
+ for ($b=0; $b<$n; $b++)
+ for ($c=0; $c<$n; $c++)
+ for ($d=0; $d<$n; $d++)
+ for ($e=0; $e<$n; $e++)
+ for ($f=0; $f<$n; $f++)
+ $x++;
+ print "$x\n";
+}
+
+/****/
+
+function sieve($n) {
+ $count = 0;
+ while ($n-- > 0) {
+ $count = 0;
+ $flags = range (0,8192);
+ for ($i=2; $i<8193; $i++) {
+ if ($flags[$i] > 0) {
+ for ($k=$i+$i; $k <= 8192; $k+=$i) {
+ $flags[$k] = 0;
+ }
+ $count++;
+ }
+ }
+ }
+ print "Count: $count\n";
+}
+
+/****/
+
+function strcat($n) {
+ $str = "";
+ while ($n-- > 0) {
+ $str .= "hello\n";
+ }
+ $len = strlen($str);
+ print "$len\n";
+}
+
+/*****/
+
+function getmicrotime()
+{
+ $t = gettimeofday();
+ return ($t['sec'] + $t['usec'] / 1000000);
+}
+
+function start_test()
+{
+ ob_start();
+ return getmicrotime();
+}
+
+function end_test($start, $name)
+{
+ global $total;
+ $end = getmicrotime();
+ ob_end_clean();
+ $total += $end-$start;
+ $num = number_format($end-$start,3);
+ $pad = str_repeat(" ", 24-strlen($name)-strlen($num));
+
+ echo $name.$pad.$num."\n";
+ ob_start();
+ return getmicrotime();
+}
+
+function total()
+{
+ global $total;
+ $pad = str_repeat("-", 24);
+ echo $pad."\n";
+ $num = number_format($total,3);
+ $pad = str_repeat(" ", 24-strlen("Total")-strlen($num));
+ echo "Total".$pad.$num."\n";
+}
+
+$t0 = $t = start_test();
+simple();
+$t = end_test($t, "simple");
+simplecall();
+$t = end_test($t, "simplecall");
+simpleucall();
+$t = end_test($t, "simpleucall");
+simpleudcall();
+$t = end_test($t, "simpleudcall");
+mandel();
+$t = end_test($t, "mandel");
+mandel2();
+$t = end_test($t, "mandel2");
+ackermann(7);
+$t = end_test($t, "ackermann(7)");
+ary(50000);
+$t = end_test($t, "ary(50000)");
+ary2(50000);
+$t = end_test($t, "ary2(50000)");
+ary3(2000);
+$t = end_test($t, "ary3(2000)");
+fibo(30);
+$t = end_test($t, "fibo(30)");
+hash1(50000);
+$t = end_test($t, "hash1(50000)");
+hash2(500);
+$t = end_test($t, "hash2(500)");
+heapsort(20000);
+$t = end_test($t, "heapsort(20000)");
+matrix(20);
+$t = end_test($t, "matrix(20)");
+nestedloop(12);
+$t = end_test($t, "nestedloop(12)");
+sieve(30);
+$t = end_test($t, "sieve(30)");
+strcat(200000);
+$t = end_test($t, "strcat(200000)");
+total($t0, "Total");
+?>
diff --git a/src/bench/micro_bench.php b/src/bench/micro_bench.php
new file mode 100644
index 00000000..70525882
--- /dev/null
+++ b/src/bench/micro_bench.php
@@ -0,0 +1,358 @@
+b;
+ }
+ }
+
+ function write_prop($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $this->b = 0;
+ }
+ }
+
+ function assign_add_prop($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $this->b += 2;
+ }
+ }
+
+ function pre_inc_prop($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ ++$this->b;
+ }
+ }
+
+ function pre_dec_prop($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ --$this->b;
+ }
+ }
+
+ function post_inc_prop($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $this->b++;
+ }
+ }
+
+ function post_dec_prop($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $this->b--;
+ }
+ }
+
+ function isset_prop($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $x = isset($this->b);
+ }
+ }
+
+ function empty_prop($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $x = empty($this->b);
+ }
+ }
+
+ function g() {
+ }
+
+ function call($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $this->g();
+ }
+ }
+
+ function read_const($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $x = $this::TEST;
+ }
+ }
+
+}
+
+function read_static($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $x = Foo::$a;
+ }
+}
+
+function write_static($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ Foo::$a = 0;
+ }
+}
+
+function isset_static($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $x = isset(Foo::$a);
+ }
+}
+
+function empty_static($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $x = empty(Foo::$a);
+ }
+}
+
+function call_static($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ Foo::f();
+ }
+}
+
+function create_object($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $x = new Foo();
+ }
+}
+
+define('TEST', null);
+
+function read_const($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $x = TEST;
+ }
+}
+
+function read_auto_global($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $x = $_GET;
+ }
+}
+
+$g_var = 0;
+
+function read_global_var($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ $x = $GLOBALS['g_var'];
+ }
+}
+
+function read_hash($n) {
+ $hash = array('test' => 0);
+ for ($i = 0; $i < $n; ++$i) {
+ $x = $hash['test'];
+ }
+}
+
+function read_str_offset($n) {
+ $str = "test";
+ for ($i = 0; $i < $n; ++$i) {
+ $x = $str[1];
+ }
+}
+
+function issetor($n) {
+ $val = array(0,1,2,3,4,5,6,7,8,9);
+ for ($i = 0; $i < $n; ++$i) {
+ $x = $val ?: null;
+ }
+}
+
+function issetor2($n) {
+ $f = false; $j = 0;
+ for ($i = 0; $i < $n; ++$i) {
+ $x = $f ?: $j + 1;
+ }
+}
+
+function ternary($n) {
+ $val = array(0,1,2,3,4,5,6,7,8,9);
+ $f = false;
+ for ($i = 0; $i < $n; ++$i) {
+ $x = $f ? null : $val;
+ }
+}
+
+function ternary2($n) {
+ $f = false; $j = 0;
+ for ($i = 0; $i < $n; ++$i) {
+ $x = $f ? $f : $j + 1;
+ }
+}
+
+/*****/
+
+function empty_loop($n) {
+ for ($i = 0; $i < $n; ++$i) {
+ }
+}
+
+function getmicrotime()
+{
+ $t = gettimeofday();
+ return ($t['sec'] + $t['usec'] / 1000000);
+}
+
+function start_test()
+{
+ ob_start();
+ return getmicrotime();
+}
+
+function end_test($start, $name, $overhead = null)
+{
+ global $total;
+ global $last_time;
+ $end = getmicrotime();
+ ob_end_clean();
+ $last_time = $end-$start;
+ $total += $last_time;
+ $num = number_format($last_time,3);
+ $pad = str_repeat(" ", 24-strlen($name)-strlen($num));
+ if (is_null($overhead)) {
+ echo $name.$pad.$num."\n";
+ } else {
+ $num2 = number_format($last_time - $overhead,3);
+ echo $name.$pad.$num." ".$num2."\n";
+ }
+ ob_start();
+ return getmicrotime();
+}
+
+function total()
+{
+ global $total;
+ $pad = str_repeat("-", 24);
+ echo $pad."\n";
+ $num = number_format($total,3);
+ $pad = str_repeat(" ", 24-strlen("Total")-strlen($num));
+ echo "Total".$pad.$num."\n";
+}
+
+const N = 5000000;
+
+$t0 = $t = start_test();
+empty_loop(N);
+$t = end_test($t, 'empty_loop');
+$overhead = $last_time;
+simpleucall(N);
+$t = end_test($t, 'func()', $overhead);
+simpleudcall(N);
+$t = end_test($t, 'undef_func()', $overhead);
+simpleicall(N);
+$t = end_test($t, 'int_func()', $overhead);
+Foo::read_static(N);
+$t = end_test($t, '$x = self::$x', $overhead);
+Foo::write_static(N);
+$t = end_test($t, 'self::$x = 0', $overhead);
+Foo::isset_static(N);
+$t = end_test($t, 'isset(self::$x)', $overhead);
+Foo::empty_static(N);
+$t = end_test($t, 'empty(self::$x)', $overhead);
+read_static(N);
+$t = end_test($t, '$x = Foo::$x', $overhead);
+write_static(N);
+$t = end_test($t, 'Foo::$x = 0', $overhead);
+isset_static(N);
+$t = end_test($t, 'isset(Foo::$x)', $overhead);
+empty_static(N);
+$t = end_test($t, 'empty(Foo::$x)', $overhead);
+Foo::call_static(N);
+$t = end_test($t, 'self::f()', $overhead);
+call_static(N);
+$t = end_test($t, 'Foo::f()', $overhead);
+$x = new Foo();
+$x->read_prop(N);
+$t = end_test($t, '$x = $this->x', $overhead);
+$x->write_prop(N);
+$t = end_test($t, '$this->x = 0', $overhead);
+$x->assign_add_prop(N);
+$t = end_test($t, '$this->x += 2', $overhead);
+$x->pre_inc_prop(N);
+$t = end_test($t, '++$this->x', $overhead);
+$x->pre_dec_prop(N);
+$t = end_test($t, '--$this->x', $overhead);
+$x->post_inc_prop(N);
+$t = end_test($t, '$this->x++', $overhead);
+$x->post_dec_prop(N);
+$t = end_test($t, '$this->x--', $overhead);
+$x->isset_prop(N);
+$t = end_test($t, 'isset($this->x)', $overhead);
+$x->empty_prop(N);
+$t = end_test($t, 'empty($this->x)', $overhead);
+$x->call(N);
+$t = end_test($t, '$this->f()', $overhead);
+$x->read_const(N);
+$t = end_test($t, '$x = Foo::TEST', $overhead);
+create_object(N);
+$t = end_test($t, 'new Foo()', $overhead);
+read_const(N);
+$t = end_test($t, '$x = TEST', $overhead);
+read_auto_global(N);
+$t = end_test($t, '$x = $_GET', $overhead);
+read_global_var(N);
+$t = end_test($t, '$x = $GLOBALS[\'v\']', $overhead);
+read_hash(N);
+$t = end_test($t, '$x = $hash[\'v\']', $overhead);
+read_str_offset(N);
+$t = end_test($t, '$x = $str[0]', $overhead);
+issetor(N);
+$t = end_test($t, '$x = $a ?: null', $overhead);
+issetor2(N);
+$t = end_test($t, '$x = $f ?: tmp', $overhead);
+ternary(N);
+$t = end_test($t, '$x = $f ? $f : $a', $overhead);
+ternary2(N);
+$t = end_test($t, '$x = $f ? $f : tmp', $overhead);
+total($t0, "Total");
diff --git a/src/config.m4 b/src/config.m4
new file mode 100644
index 00000000..aba355c0
--- /dev/null
+++ b/src/config.m4
@@ -0,0 +1,35 @@
+dnl $Id$
+dnl config.m4 for extension snuffleupagus
+
+sources="snuffleupagus.c sp_config.c sp_config_utils.c sp_harden_rand.c"
+sources="$sources sp_unserialize.c sp_utils.c sp_disable_xxe.c sp_list.c"
+sources="$sources sp_disabled_functions.c sp_execute.c sp_upload_validation.c"
+sources="$sources sp_cookie_encryption.c sp_network_utils.c tweetnacl.c"
+sources="$sources sp_config_keywords.c sp_compile.c"
+
+PHP_ARG_ENABLE(snuffleupagus, whether to enable snuffleupagus support,
+[ --enable-snuffleupagus Enable snuffleupagus support])
+
+PHP_ARG_ENABLE(coverage, whether to enable coverage support,
+[ --enable-coverage Enable coverage support])
+
+PHP_ARG_ENABLE(debug, whether to enable debug messages,
+[ --enable-debug Enable debug messages])
+
+CFLAGS="$CFLAGS -lpcre"
+CFLAGS="$CFLAGS -D_DEFAULT_SOURCE=1 -std=c99"
+CFLAGS="$CFLAGS -Wall -Wextra -Wno-unused-parameter"
+
+if test "$PHP_DEBUG" = "yes"; then
+ AC_DEFINE(SP_DEBUG, 1, [Wether you want to enable debug messages])
+fi
+
+if test "$PHP_SNUFFLEUPAGUS" != "no"; then
+ if test "$PHP_COVERAGE" != "no"; then
+ CFLAGS="$CFLAGS --coverage -fprofile-arcs -ftest-coverage"
+ LDFLAGS="$LDFLAGS --coverage"
+ PHP_NEW_EXTENSION(snuffleupagus, $sources, $ext_shared,-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 -g -fprofile-arcs -ftest-coverage -lgcov)
+ else
+ PHP_NEW_EXTENSION(snuffleupagus, $sources, $ext_shared,-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
+ fi
+fi
diff --git a/src/config.w32 b/src/config.w32
new file mode 100644
index 00000000..a0197c1f
--- /dev/null
+++ b/src/config.w32
@@ -0,0 +1,13 @@
+// $Id$
+// vim:ft=javascript
+
+// If your extension references something external, use ARG_WITH
+// ARG_WITH("snuffleupagus", "for snuffleupagus support", "no");
+
+// Otherwise, use ARG_ENABLE
+// ARG_ENABLE("snuffleupagus", "enable snuffleupagus support", "no");
+
+if (PHP_SNUFFLEUPAGUS != "no") {
+ EXTENSION("snuffleupagus", "snuffleupagus.c", PHP_EXTNAME_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
+}
+
diff --git a/src/php_snuffleupagus.h b/src/php_snuffleupagus.h
new file mode 100644
index 00000000..e7a3d599
--- /dev/null
+++ b/src/php_snuffleupagus.h
@@ -0,0 +1,71 @@
+#ifndef PHP_SNUFFLEUPAGUS_H
+#define PHP_SNUFFLEUPAGUS_H
+
+#define PHP_SNUFFLEUPAGUS_VERSION "0.1"
+#define PHP_SNUFFLEUPAGUS_EXTNAME "snuffleupagus"
+#define PHP_SNUFFLEUPAGUS_AUTHOR "NBS System"
+#define PHP_SNUFFLEUPAGUS_URL "https://github.com/nbs-system/snuffleupagus"
+#define PHP_SNUFFLEUPAGUS_COPYRIGHT "LGPLv2"
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include "SAPI.h"
+#include "ext/standard/info.h"
+#include "php.h"
+#include "php_ini.h"
+#include "zend_hash.h"
+#include "zend_string.h"
+#include "zend_extensions.h"
+
+#include "sp_list.h"
+#include "sp_compile.h"
+#include "sp_config.h"
+#include "sp_config_utils.h"
+#include "sp_config_keywords.h"
+#include "sp_cookie_encryption.h"
+#include "sp_disable_xxe.h"
+#include "sp_disabled_functions.h"
+#include "sp_execute.h"
+#include "sp_harden_rand.h"
+#include "sp_network_utils.h"
+#include "sp_unserialize.h"
+#include "sp_upload_validation.h"
+#include "sp_utils.h"
+
+extern zend_module_entry snuffleupagus_module_entry;
+#define phpext_snuffleupagus_ptr &snuffleupagus_module_entry
+
+#ifdef PHP_WIN32
+#define PHP_SNUFFLEUPAGUS_API __declspec(dllexport)
+#elif defined(__GNUC__) && __GNUC__ >= 4
+#define PHP_SNUFFLEUPAGUS_API __attribute__((visibility("default")))
+#else
+#define PHP_SNUFFLEUPAGUS_API
+#endif
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+ZEND_BEGIN_MODULE_GLOBALS(snuffleupagus)
+sp_config config;
+HashTable *disabled_functions_hook;
+HashTable *sp_internal_functions_hook;
+ZEND_END_MODULE_GLOBALS(snuffleupagus)
+
+#define SNUFFLEUPAGUS_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(snuffleupagus, v)
+
+#if defined(ZTS) && defined(COMPILE_DL_SNUFFLEUPAGUS)
+ZEND_TSRMLS_CACHE_EXTERN()
+#endif
+
+PHP_FUNCTION(check_disabled_function);
+
+static inline void sp_terminate() { zend_bailout(); }
+
+#endif /* PHP_SNUFFLEUPAGUS_H */
diff --git a/src/snuffleupagus.c b/src/snuffleupagus.c
new file mode 100644
index 00000000..52b975ed
--- /dev/null
+++ b/src/snuffleupagus.c
@@ -0,0 +1,222 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php_snuffleupagus.h"
+
+#ifndef ZEND_EXT_API
+#define ZEND_EXT_API ZEND_DLEXPORT
+#endif
+
+static PHP_INI_MH(OnUpdateConfiguration);
+static inline int zend_auto_start(zend_extension *extension);
+static inline void sp_op_array_handler(zend_op_array *op);
+
+ZEND_EXTENSION();
+
+ZEND_DLEXPORT int sp_zend_startup(zend_extension *extension) {
+ return zend_startup_module(&snuffleupagus_module_entry);
+}
+
+static inline void sp_op_array_handler(zend_op_array *op) {
+ if (NULL == op->filename) {
+ return;
+ } else {
+ op->fn_flags |= ZEND_ACC_STRICT_TYPES;
+ }
+}
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
+
+PHP_INI_BEGIN()
+PHP_INI_ENTRY("sp.configuration_file", "", PHP_INI_SYSTEM,
+ OnUpdateConfiguration)
+PHP_INI_END()
+
+ZEND_DLEXPORT zend_extension zend_extension_entry = {
+ PHP_SNUFFLEUPAGUS_EXTNAME,
+ PHP_SNUFFLEUPAGUS_VERSION,
+ PHP_SNUFFLEUPAGUS_AUTHOR,
+ PHP_SNUFFLEUPAGUS_URL,
+ PHP_SNUFFLEUPAGUS_COPYRIGHT,
+ sp_zend_startup,
+ NULL,
+ NULL, /* activate_func_t */
+ NULL, /* deactivate_func_t */
+ NULL, /* message_handler_func_t */
+ sp_op_array_handler,//zend_global_strict, /* op_array_handler_func_t */
+ NULL, /* statement_handler_func_t */
+ NULL, /* fcall_begin_handler_func_t */
+ NULL, /* fcall_end_handler_func_t */
+ NULL, /* op_array_ctor_func_t */
+ NULL, /* op_array_dtor_func_t */
+ STANDARD_ZEND_EXTENSION_PROPERTIES};
+
+/* Uncomment this function if you have INI entries
+static void php_snuffleupagus_init_globals(zend_snuffleupagus_globals
+*snuffleupagus_globals)
+{
+ snuffleupagus_globals->global_value = 0;
+ snuffleupagus_globals->global_string = NULL;
+}
+*/
+
+PHP_GINIT_FUNCTION(snuffleupagus) {
+#define SP_INIT(F) F = pecalloc(sizeof(*F), 1, 1);
+#define SP_INIT_HT(F) \
+ F = pemalloc(sizeof(*F), 1); \
+ zend_hash_init(F, 10, NULL, NULL, 1);
+
+ SP_INIT_HT(snuffleupagus_globals->disabled_functions_hook);
+ SP_INIT_HT(snuffleupagus_globals->sp_internal_functions_hook);
+
+ SP_INIT(snuffleupagus_globals->config.config_unserialize);
+ SP_INIT(snuffleupagus_globals->config.config_random);
+ SP_INIT(snuffleupagus_globals->config.config_readonly_exec);
+ SP_INIT(snuffleupagus_globals->config.config_global_strict);
+ SP_INIT(snuffleupagus_globals->config.config_auto_cookie_secure);
+ SP_INIT(snuffleupagus_globals->config.config_snuffleupagus);
+ SP_INIT(snuffleupagus_globals->config.config_disable_xxe);
+ SP_INIT(snuffleupagus_globals->config.config_upload_validation);
+ SP_INIT(snuffleupagus_globals->config.config_disabled_functions);
+ SP_INIT(snuffleupagus_globals->config.config_disabled_functions_ret);
+ SP_INIT(snuffleupagus_globals->config.config_cookie_encryption);
+ SP_INIT(snuffleupagus_globals->config.config_regexp_inclusion);
+
+ snuffleupagus_globals->config.config_regexp_inclusion->regexp_inclusion = sp_new_list();
+ snuffleupagus_globals->config.config_disabled_functions->disabled_functions = sp_new_list();
+ snuffleupagus_globals->config.config_disabled_functions_ret->disabled_functions = sp_new_list();
+
+ SP_INIT_HT(snuffleupagus_globals->config.config_cookie_encryption->names);
+
+#undef SP_INIT
+#undef SP_INIT_HT
+}
+
+PHP_MINIT_FUNCTION(snuffleupagus) {
+ REGISTER_INI_ENTRIES();
+
+ return SUCCESS;
+}
+
+PHP_MSHUTDOWN_FUNCTION(snuffleupagus) {
+#define FREE_HT(F) \
+ zend_hash_destroy(SNUFFLEUPAGUS_G(F)); \
+ pefree(SNUFFLEUPAGUS_G(F), 1);
+
+ FREE_HT(disabled_functions_hook);
+ FREE_HT(config.config_cookie_encryption->names);
+
+#undef FREE_HT
+
+ pefree(SNUFFLEUPAGUS_G(config.config_unserialize), 1);
+ pefree(SNUFFLEUPAGUS_G(config.config_random), 1);
+ pefree(SNUFFLEUPAGUS_G(config.config_readonly_exec), 1);
+ pefree(SNUFFLEUPAGUS_G(config.config_global_strict), 1);
+ pefree(SNUFFLEUPAGUS_G(config.config_auto_cookie_secure), 1);
+ pefree(SNUFFLEUPAGUS_G(config.config_snuffleupagus), 1);
+ pefree(SNUFFLEUPAGUS_G(config.config_disable_xxe), 1);
+ pefree(SNUFFLEUPAGUS_G(config.config_upload_validation), 1);
+ pefree(SNUFFLEUPAGUS_G(config.config_cookie_encryption), 1);
+
+ sp_list_free(SNUFFLEUPAGUS_G(config.config_disabled_functions->disabled_functions));
+ pefree(SNUFFLEUPAGUS_G(config.config_disabled_functions), 1);
+ sp_list_free(SNUFFLEUPAGUS_G(config.config_disabled_functions_ret->disabled_functions));
+ pefree(SNUFFLEUPAGUS_G(config.config_disabled_functions_ret), 1);
+
+ UNREGISTER_INI_ENTRIES();
+
+ return SUCCESS;
+}
+
+PHP_RINIT_FUNCTION(snuffleupagus) {
+#if defined(COMPILE_DL_SNUFFLEUPAGUS) && defined(ZTS)
+ ZEND_TSRMLS_CACHE_UPDATE();
+#endif
+ if (NULL != SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key) {
+ if (NULL != SNUFFLEUPAGUS_G(config).config_cookie_encryption->names) {
+ zend_hash_apply_with_arguments(
+ Z_ARRVAL(PG(http_globals)[TRACK_VARS_COOKIE]), decrypt_cookie, 0);
+ }
+ }
+ return SUCCESS;
+}
+
+PHP_RSHUTDOWN_FUNCTION(snuffleupagus) { return SUCCESS; }
+
+PHP_MINFO_FUNCTION(snuffleupagus) {
+ php_info_print_table_start();
+ php_info_print_table_header(2, "snuffleupagus support", "enabled");
+ php_info_print_table_end();
+
+ /* Remove comments if you have entries in php.ini
+ DISPLAY_INI_ENTRIES();
+ */
+}
+
+static PHP_INI_MH(OnUpdateConfiguration) {
+ TSRMLS_FETCH();
+
+ if (!new_value || !new_value->len) {
+ return FAILURE;
+ }
+
+ if (sp_parse_config(new_value->val) != SUCCESS) {
+ return FAILURE;
+ }
+
+ if (SNUFFLEUPAGUS_G(config).config_random->enable) {
+ hook_rand();
+ }
+ if (SNUFFLEUPAGUS_G(config).config_upload_validation->enable) {
+ hook_upload();
+ }
+ if (SNUFFLEUPAGUS_G(config).config_disable_xxe->enable == 0) {
+ hook_libxml_disable_entity_loader();
+ }
+ hook_disabled_functions();
+ hook_execute();
+
+ if (NULL != SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key) {
+ if (SNUFFLEUPAGUS_G(config).config_unserialize->enable) {
+ hook_serialize();
+ }
+ hook_cookies();
+ }
+
+ if (true == SNUFFLEUPAGUS_G(config).config_global_strict->enable) {
+ if (!zend_get_extension(PHP_SNUFFLEUPAGUS_EXTNAME)) {
+ zend_extension_entry.startup = NULL;
+ zend_register_extension(&zend_extension_entry, NULL);
+ }
+ // This is needed to implement the global strict mode
+ CG(compiler_options) |= ZEND_COMPILE_HANDLE_OP_ARRAY;
+ }
+
+ return SUCCESS;
+}
+
+const zend_function_entry snuffleupagus_functions[] = {PHP_FE_END};
+
+zend_module_entry snuffleupagus_module_entry =
+ {STANDARD_MODULE_HEADER,
+ PHP_SNUFFLEUPAGUS_EXTNAME,
+ snuffleupagus_functions,
+ PHP_MINIT(snuffleupagus),
+ PHP_MSHUTDOWN(snuffleupagus),
+ PHP_RINIT(snuffleupagus),
+ PHP_RSHUTDOWN(snuffleupagus),
+ PHP_MINFO(snuffleupagus),
+ PHP_SNUFFLEUPAGUS_VERSION,
+ PHP_MODULE_GLOBALS(snuffleupagus),
+ PHP_GINIT(snuffleupagus),
+ NULL,
+ NULL,
+ STANDARD_MODULE_PROPERTIES_EX};
+
+#ifdef COMPILE_DL_SNUFFLEUPAGUS
+#ifdef ZTS
+ZEND_TSRMLS_CACHE_DEFINE()
+#endif
+ZEND_GET_MODULE(snuffleupagus)
+#endif
diff --git a/src/snuffleupagus.php b/src/snuffleupagus.php
new file mode 100644
index 00000000..b373a531
--- /dev/null
+++ b/src/snuffleupagus.php
@@ -0,0 +1,21 @@
+";
+
+if(!extension_loaded('snuffleupagus')) {
+ dl('snuffleupagus.' . PHP_SHLIB_SUFFIX);
+}
+$module = 'snuffleupagus';
+$functions = get_extension_funcs($module);
+echo "Functions available in the test extension:$br\n";
+foreach($functions as $func) {
+ echo $func."$br\n";
+}
+echo "$br\n";
+$function = 'confirm_' . $module . '_compiled';
+if (extension_loaded($module)) {
+ $str = $function($module);
+} else {
+ $str = "Module $module is not compiled into PHP";
+}
+echo "$str\n";
+?>
diff --git a/src/sp_compile.c b/src/sp_compile.c
new file mode 100644
index 00000000..2902377e
--- /dev/null
+++ b/src/sp_compile.c
@@ -0,0 +1,53 @@
+#include "php_snuffleupagus.h"
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus);
+
+static zend_op_array *(*orig_compile_file)(zend_file_handle *, int);
+static zend_op_array *(*orig_compile_string)(zval *, char *);
+
+zend_op_array *sp_compile_file(zend_file_handle *file_handle, int type) {
+ zend_op_array *ret = orig_compile_file(file_handle, type);
+
+ const sp_node_t* config = SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions;
+
+ while (config && config->data) {
+ const char* function_name = ((sp_disabled_function*)config->data)->function;
+ const pcre* function_name_regexp = ((sp_disabled_function*)config->data)->r_function;
+
+ sp_log_err("checking for %s", function_name);
+ // EG(function_table)->arData[count - 1].val.value.func->internal_function.handler = PHP_FN(check_disabled_function);
+
+ if (function_name) {
+ if (HOOK_FUNCTION(function_name, disabled_functions_hook, PHP_FN(check_disabled_function), true) == SUCCESS) {
+ sp_log_err("Successfully hooked %s", function_name);
+ }
+ break;
+ } else if (function_name_regexp) {
+ sp_log_err("error", "We'll hook regard later.");
+ }
+ config = config->next;
+ }
+
+ return ret;
+}
+
+zend_op_array *sp_compile_string(zval *source_string, char *filename) {
+ zend_op_array *ret = orig_compile_string(source_string, filename);
+ sp_log_err("in compile_string : filename is :%s", filename);
+ return ret;
+}
+
+
+int hook_compile(void) {
+ TSRMLS_FETCH();
+
+ /* zend_compile_file is used to compile php file */
+ orig_compile_file = zend_compile_file;
+ zend_compile_file = sp_compile_file;
+
+ /* zend_compile_string is used to compile php string */
+ orig_compile_string = zend_compile_string;
+ zend_compile_string = sp_compile_string;
+
+ return SUCCESS;
+}
\ No newline at end of file
diff --git a/src/sp_compile.h b/src/sp_compile.h
new file mode 100644
index 00000000..538ff524
--- /dev/null
+++ b/src/sp_compile.h
@@ -0,0 +1,6 @@
+#ifndef SP_COMPILE_H
+#define SP_COMPILE_H
+
+int hook_compile(void);
+
+#endif /* SP_COMPILE_H */
\ No newline at end of file
diff --git a/src/sp_config.c b/src/sp_config.c
new file mode 100644
index 00000000..f73347dc
--- /dev/null
+++ b/src/sp_config.c
@@ -0,0 +1,193 @@
+#include
+#include
+#include
+
+#include "php_snuffleupagus.h"
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
+
+sp_config_tokens const sp_func[] = {
+ {.func = parse_unserialize, .token = SP_TOKEN_UNSERIALIZE_HMAC},
+ {.func = parse_random, .token = SP_TOKEN_HARDEN_RANDOM},
+ {.func = parse_disabled_functions, .token = SP_TOKEN_DISABLE_FUNC},
+ {.func = parse_readonly_exec, .token = SP_TOKEN_READONLY_EXEC},
+ {.func = parse_global_strict, .token = SP_TOKEN_GLOBAL_STRICT},
+ {.func = parse_upload_validation, .token = SP_TOKEN_UPLOAD_VALIDATION},
+ {.func = parse_cookie_encryption, .token = SP_TOKEN_COOKIE_ENCRYPTION},
+ {.func = parse_global, .token = SP_TOKEN_GLOBAL},
+ {.func = parse_auto_cookie_secure, .token = SP_TOKEN_AUTO_COOKIE_SECURE},
+ {.func = parse_disable_xxe, .token = SP_TOKEN_DISABLE_XXE},
+ {NULL, NULL}};
+
+/* Top level keyword parsing */
+
+static int parse_line(char *line) {
+ char *ptr = line;
+
+ while (*ptr == ' ' || *ptr == '\t') {
+ ++ptr;
+ }
+
+ if (!*ptr || *ptr == '#' || *ptr == ';') {
+ return 0;
+ }
+
+ if (strncmp(ptr, SP_TOKEN_BASE, strlen(SP_TOKEN_BASE))) {
+ sp_log_err("config", "Invalid configuration prefix for '%s'.", line);
+ return -1;
+ }
+ ptr += strlen(SP_TOKEN_BASE);
+
+ for (size_t i = 0; sp_func[i].func; i++) {
+ if (!strncmp(sp_func[i].token, ptr, strlen(sp_func[i].token))) {
+ return sp_func[i].func(ptr + strlen(sp_func[i].token));
+ }
+ }
+ sp_log_err("config", "Invalid configuration section '%s'.", line);
+ return -1;
+}
+
+/* keyword parsing */
+int parse_empty(char *restrict line, char *restrict keyword, void *retval) {
+ *(bool *)retval = true;
+ return 0;
+}
+
+int parse_int(char *restrict line, char *restrict keyword, void *retval) {
+ size_t consumed = 0;
+ char *value = get_param(&consumed, line, SP_TYPE_INT, keyword);
+ if (value) {
+ sscanf(value, "%ud", (uint32_t *)retval);
+ pefree(value, 1);
+ return consumed;
+ } else {
+ sp_log_err("error", "%s) is expecting a valid integer.", keyword);
+ return -1;
+ }
+}
+
+int parse_php_type(char *restrict line, char *restrict keyword, void *retval) {
+ size_t consumed = 0;
+ char *value = get_param(&consumed, line, SP_TYPE_STR, keyword);
+ if (value) {
+ if (0 == strcasecmp("undef", value)) {
+ *(sp_php_type*)retval = SP_PHP_TYPE_UNDEF;
+ } else if (0 == strcasecmp("null", value)) {
+ *(sp_php_type*)retval = SP_PHP_TYPE_NULL;
+ } else if (0 == strcasecmp("true", value)) {
+ *(sp_php_type*)retval = SP_PHP_TYPE_TRUE;
+ } else if (0 == strcasecmp("false", value)) {
+ *(sp_php_type*)retval = SP_PHP_TYPE_FALSE;
+ } else if (0 == strcasecmp("long", value)) {
+ *(sp_php_type*)retval = SP_PHP_TYPE_LONG;
+ } else if (0 == strcasecmp("double", value)) {
+ *(sp_php_type*)retval = SP_PHP_TYPE_DOUBLE;
+ } else if (0 == strcasecmp("string", value)) {
+ *(sp_php_type*)retval = SP_PHP_TYPE_STRING;
+ } else if (0 == strcasecmp("array", value)) {
+ *(sp_php_type*)retval = SP_PHP_TYPE_ARRAY;
+ } else if (0 == strcasecmp("object", value)) {
+ *(sp_php_type*)retval = SP_PHP_TYPE_OBJECT;
+ } else if (0 == strcasecmp("resource", value)) {
+ *(sp_php_type*)retval = SP_PHP_TYPE_RESOURCE;
+ } else if (0 == strcasecmp("reference", value)) {
+ *(sp_php_type*)retval = SP_PHP_TYPE_REFERENCE;
+ } else {
+ pefree(value, 1);
+ sp_log_err("error", "%s) is expecting a valid php type ('false', 'true',"
+ " 'array'. 'object', 'long', 'double', 'null', 'resource', 'reference',"
+ " 'undef').", keyword);
+ return -1;
+ }
+ pefree(value, 1);
+ return consumed;
+ } else {
+ return -1;
+ }
+}
+
+int parse_str(char *restrict line, char *restrict keyword, void *retval) {
+ char *value = NULL;
+
+ size_t consumed = 0;
+ value = get_param(&consumed, line, SP_TYPE_STR, keyword);
+ if (value) {
+ *(char **)retval = value;
+ return consumed;
+ }
+ return -1;
+}
+
+int parse_cidr(char *restrict line, char *restrict keyword, void *retval) {
+ size_t consumed = 0;
+ char *value = get_param(&consumed, line, SP_TYPE_STR, keyword);
+ sp_cidr *cidr = pecalloc(sizeof(sp_cidr), 1, 1);
+
+ if (value) {
+ if (-1 == get_ip_and_cidr(value, cidr)) {
+ return -1;
+ }
+ *(sp_cidr **)retval = cidr;
+ return consumed;
+ } else {
+ sp_log_err("config", "%s doesn't contain a valid cidr.", line);
+ return -1;
+ }
+}
+
+int parse_regexp(char *restrict line, char *restrict keyword, void *retval) {
+ /* TODO: Do we want to use pcre_study?
+ * (http://www.pcre.org/original/doc/html/pcre_study.html)
+ * maybe not: http://sljit.sourceforge.net/pcre.html*/
+ size_t consumed = 0;
+ char *value = get_param(&consumed, line, SP_TYPE_STR, keyword);
+
+ if (value) {
+ const char *pcre_error;
+ int pcre_error_offset;
+ pcre *compiled_re = pcre_compile(value, PCRE_CASELESS, &pcre_error,
+ &pcre_error_offset, NULL);
+ if (NULL == compiled_re) {
+ sp_log_err("config", "Failed to compile '%s': %s.", value, pcre_error);
+ } else {
+ *(pcre **)retval = compiled_re;
+ return consumed;
+ }
+ }
+ char *closing_paren = strchr(line, ')');
+ if (NULL != closing_paren) {
+ closing_paren[0] = '\0';
+ }
+ sp_log_err("config", "'%s)' is expecting a valid regexp, and not '%s'.",
+ keyword, line);
+ return -1;
+}
+
+int sp_parse_config(const char *conf_file) {
+ FILE *fd = fopen(conf_file, "r");
+ char *lineptr = NULL;
+ size_t n = 0;
+
+ if (fd == NULL) {
+ sp_log_err("config", "Could not open configuration file %s : %s", conf_file,
+ strerror(errno));
+ return FAILURE;
+ }
+
+ while (getline(&lineptr, &n, fd) > 0) {
+ /* We trash the terminal `\n`. This simplify the display of logs. */
+ if (lineptr[strlen(lineptr) - 1] == '\n') {
+ lineptr[strlen(lineptr) - 1] = '\0';
+ }
+ if (parse_line(lineptr) == -1) {
+ fclose(fd);
+ free(lineptr);
+ return FAILURE;
+ }
+ free(lineptr);
+ lineptr = NULL;
+ n = 0;
+ }
+ fclose(fd);
+ return SUCCESS;
+}
diff --git a/src/sp_config.h b/src/sp_config.h
new file mode 100644
index 00000000..54ec2cc8
--- /dev/null
+++ b/src/sp_config.h
@@ -0,0 +1,206 @@
+#ifndef SP_CONFIG_H
+#define SP_CONFIG_H
+
+#include
+#include
+#include
+
+typedef enum {
+ SP_TYPE_STR = 0,
+ SP_TYPE_REGEXP,
+ SP_TYPE_INT,
+ SP_TYPE_EMPTY
+} sp_type;
+
+typedef enum {
+ SP_PHP_TYPE_UNDEF = IS_UNDEF,
+ SP_PHP_TYPE_NULL = IS_NULL,
+ SP_PHP_TYPE_FALSE = IS_FALSE,
+ SP_PHP_TYPE_TRUE = IS_TRUE,
+ SP_PHP_TYPE_LONG = IS_LONG,
+ SP_PHP_TYPE_DOUBLE = IS_DOUBLE,
+ SP_PHP_TYPE_STRING = IS_STRING,
+ SP_PHP_TYPE_ARRAY = IS_ARRAY,
+ SP_PHP_TYPE_OBJECT = IS_OBJECT,
+ SP_PHP_TYPE_RESOURCE = IS_RESOURCE,
+ SP_PHP_TYPE_REFERENCE = IS_REFERENCE
+} sp_php_type;
+
+typedef struct {
+ int ip_version;
+ union {
+ struct in_addr ipv4;
+ struct in6_addr ipv6;
+ } ip;
+ uint8_t mask;
+} sp_cidr;
+
+typedef struct { char *encryption_key; } sp_config_encryption_key;
+
+typedef struct {
+ bool enable;
+ bool simulation;
+} sp_config_readonly_exec;
+
+typedef struct { bool enable; } sp_config_global_strict;
+
+typedef struct { bool enable; } sp_config_random;
+
+typedef struct { bool enable; } sp_config_auto_cookie_secure;
+
+typedef struct { bool enable; } sp_config_disable_xxe;
+
+typedef struct {
+ HashTable *names;
+ uint32_t mask_ipv4;
+ uint32_t mask_ipv6;
+} sp_config_cookie_encryption;
+
+typedef struct {
+ bool enable;
+ bool simulation;
+} sp_config_unserialize;
+
+typedef struct {
+ char *filename;
+ pcre *r_filename;
+
+ char *function;
+ pcre *r_function;
+
+ char *hash;
+ int simulation;
+ bool enable;
+
+ char *param;
+ pcre *r_param;
+ sp_php_type param_type;
+
+ char *ret;
+ pcre *r_ret;
+ sp_php_type ret_type;
+
+ pcre *regexp;
+ char *value;
+
+ char *dump;
+ char *alias;
+ bool param_is_array;
+ bool var_is_array;
+ sp_node_t *param_array_keys;
+ sp_node_t *var_array_keys;
+
+ bool allow;
+ bool drop;
+
+ char *var;
+
+ sp_cidr *cidr;
+} sp_disabled_function;
+
+typedef struct {
+ sp_node_t *disabled_functions; // list of sp_disabled_function
+} sp_config_disabled_functions;
+
+typedef struct {
+ sp_node_t *regexp_inclusion; // list of regexp for inclusion
+} sp_config_regexp_inclusion;
+
+typedef struct {
+ char *script;
+ bool simulation;
+ bool enable;
+} sp_config_upload_validation;
+
+typedef struct {
+ sp_config_random *config_random;
+ sp_config_unserialize *config_unserialize;
+ sp_config_disabled_functions *config_disabled_functions;
+ sp_config_disabled_functions *config_disabled_functions_ret;
+ sp_config_readonly_exec *config_readonly_exec;
+ sp_config_upload_validation *config_upload_validation;
+ sp_config_cookie_encryption *config_cookie_encryption;
+ sp_config_encryption_key *config_snuffleupagus;
+ sp_config_auto_cookie_secure *config_auto_cookie_secure;
+ sp_config_global_strict *config_global_strict;
+ sp_config_disable_xxe *config_disable_xxe;
+ sp_config_regexp_inclusion *config_regexp_inclusion;
+} sp_config;
+
+typedef struct {
+ int (*func)(char *, char *, void *);
+ char *token;
+ void *retval;
+} sp_config_functions;
+
+typedef struct {
+ int (*func)(char *);
+ char *token;
+} sp_config_tokens;
+
+#define SP_TOKEN_BASE "sp"
+
+#define SP_TOKEN_AUTO_COOKIE_SECURE ".auto_cookie_secure"
+#define SP_TOKEN_COOKIE_ENCRYPTION ".cookie_encryption"
+#define SP_TOKEN_DISABLE_FUNC ".disable_functions"
+#define SP_TOKEN_GLOBAL ".global"
+#define SP_TOKEN_GLOBAL_STRICT ".global_strict"
+#define SP_TOKEN_HARDEN_RANDOM ".harden_random"
+#define SP_TOKEN_READONLY_EXEC ".readonly_exec"
+#define SP_TOKEN_UNSERIALIZE_HMAC ".unserialize_hmac"
+#define SP_TOKEN_UPLOAD_VALIDATION ".upload_validation"
+#define SP_TOKEN_DISABLE_XXE ".disable_xxe"
+
+// common tokens
+#define SP_TOKEN_ENABLE ".enable("
+#define SP_TOKEN_DISABLE ".disable("
+#define SP_TOKEN_SIMULATION ".simulation("
+#define SP_TOKEN_TRUE "1"
+#define SP_TOKEN_FALSE "0"
+#define SP_TOKEN_DUMP ".dump("
+#define SP_TOKEN_ALIAS ".alias("
+#define SP_TOKEN_ALLOW ".allow("
+#define SP_TOKEN_DROP ".drop("
+
+#define SP_TOKEN_END_PARAM ')'
+
+// disable_function
+#define SP_TOKEN_CIDR ".cidr("
+#define SP_TOKEN_FILENAME ".filename("
+#define SP_TOKEN_FILENAME_REGEXP ".filename_r("
+#define SP_TOKEN_FUNCTION ".function("
+#define SP_TOKEN_FUNCTION_REGEXP ".function_r("
+#define SP_TOKEN_HASH ".hash("
+#define SP_TOKEN_LOCAL_VAR ".var("
+#define SP_TOKEN_PARAM ".param("
+#define SP_TOKEN_PARAM_REGEXP ".param_r("
+#define SP_TOKEN_PARAM_TYPE ".param_type("
+#define SP_TOKEN_RET ".ret("
+#define SP_TOKEN_RET_REGEXP ".ret_r("
+#define SP_TOKEN_RET_TYPE ".ret_type("
+#define SP_TOKEN_VALUE ".value("
+#define SP_TOKEN_VALUE_REGEXP ".value_r("
+
+// cookies encryption
+#define SP_TOKEN_NAME ".cookie("
+#define SP_TOKEN_MASK_IPV4 ".mask_ipv4("
+#define SP_TOKEN_MASK_IPV6 ".mask_ipv6("
+
+// Global configuration options
+#define SP_TOKEN_ENCRYPTION_KEY ".secret_key("
+
+// upload_validator
+#define SP_TOKEN_UPLOAD_SCRIPT ".script("
+
+int sp_parse_config(const char *);
+int parse_array(sp_disabled_function *);
+
+int parse_str(char *restrict, char *restrict, void *);
+int parse_regexp(char *restrict, char *restrict, void *);
+int parse_empty(char *restrict, char *restrict, void *);
+int parse_int(char *restrict, char *restrict, void *);
+int parse_cidr(char *restrict, char *restrict, void *);
+int parse_php_type(char *restrict, char *restrict, void *);
+
+
+#endif /* SP_CONFIG_H */
diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c
new file mode 100644
index 00000000..4a6dd3ad
--- /dev/null
+++ b/src/sp_config_keywords.c
@@ -0,0 +1,268 @@
+#include "php_snuffleupagus.h"
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
+
+static int parse_enable(char *line, bool * restrict retval, bool * restrict simulation) {
+ bool enable = false, disable = false;
+ sp_config_functions sp_config_funcs[] = {
+ {parse_empty, SP_TOKEN_ENABLE, &(enable)},
+ {parse_empty, SP_TOKEN_DISABLE, &(disable)},
+ {parse_empty, SP_TOKEN_SIMULATION, simulation},
+ {0}};
+
+ int ret = parse_keywords(sp_config_funcs, line);
+
+ if (0 != ret) {
+ return ret;
+ }
+
+ if (!(enable ^ disable)) {
+ sp_log_err("config", "A rule can't be enabled and disabled.");
+ return -1;
+ }
+
+ *retval = enable;
+
+ return ret;
+}
+
+int parse_random(char *line) {
+ return parse_enable(line, &(SNUFFLEUPAGUS_G(config).config_random->enable), NULL);
+}
+
+int parse_disable_xxe(char *line) {
+ return parse_enable(line, &(SNUFFLEUPAGUS_G(config).config_disable_xxe->enable), NULL);
+}
+
+int parse_auto_cookie_secure(char *line) {
+ return parse_enable(line, &(SNUFFLEUPAGUS_G(config).config_auto_cookie_secure->enable), NULL);
+}
+
+int parse_global_strict(char *line) {
+ return parse_enable(line, &(SNUFFLEUPAGUS_G(config).config_global_strict->enable), NULL);
+}
+
+int parse_unserialize(char *line) {
+ return parse_enable(line, &(SNUFFLEUPAGUS_G(config).config_unserialize->enable), &(SNUFFLEUPAGUS_G(config).config_unserialize->simulation));
+}
+
+int parse_readonly_exec(char *line) {
+ return parse_enable(line, &(SNUFFLEUPAGUS_G(config).config_readonly_exec->enable), &(SNUFFLEUPAGUS_G(config).config_readonly_exec->simulation));
+}
+
+int parse_global(char *line) {
+ sp_config_functions sp_config_funcs_encryption_key[] = {
+ {parse_str, SP_TOKEN_ENCRYPTION_KEY,
+ &(SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key)},
+ {0}};
+ return parse_keywords(sp_config_funcs_encryption_key, line);
+}
+
+int parse_cookie_encryption(char *line) {
+ int ret = 0;
+ char *name = NULL;
+
+ sp_config_functions sp_config_funcs_cookie_encryption[] = {
+ {parse_str, SP_TOKEN_NAME, &name},
+ {parse_int, SP_TOKEN_MASK_IPV4,
+ &(SNUFFLEUPAGUS_G(config).config_cookie_encryption->mask_ipv4)},
+ {parse_int, SP_TOKEN_MASK_IPV6,
+ &(SNUFFLEUPAGUS_G(config).config_cookie_encryption->mask_ipv6)},
+ {0}};
+
+ ret = parse_keywords(sp_config_funcs_cookie_encryption, line);
+ if (0 != ret) {
+ return ret;
+ }
+
+ if (32 < SNUFFLEUPAGUS_G(config).config_cookie_encryption->mask_ipv4) {
+ SNUFFLEUPAGUS_G(config).config_cookie_encryption->mask_ipv4 = 32;
+ }
+ if (128 < SNUFFLEUPAGUS_G(config).config_cookie_encryption->mask_ipv6) {
+ SNUFFLEUPAGUS_G(config).config_cookie_encryption->mask_ipv6 = 128;
+ }
+
+ if (name) {
+ zend_hash_str_add_empty_element(
+ SNUFFLEUPAGUS_G(config).config_cookie_encryption->names, name,
+ strlen(name));
+ }
+ return SUCCESS;
+}
+
+int parse_disabled_functions(char *line) {
+ int ret = 0;
+ bool enable = true, disable = false;
+ sp_disabled_function *df = pecalloc(sizeof(*df), 1, 1);
+
+ sp_config_functions sp_config_funcs_disabled_functions[] = {
+ {parse_empty, SP_TOKEN_ENABLE, &(enable)},
+ {parse_empty, SP_TOKEN_DISABLE, &(disable)},
+ {parse_str, SP_TOKEN_ALIAS, &(df->alias)},
+ {parse_empty, SP_TOKEN_SIMULATION, &(df->simulation)},
+ {parse_str, SP_TOKEN_FILENAME, &(df->filename)},
+ {parse_regexp, SP_TOKEN_FILENAME_REGEXP, &(df->r_filename)},
+ {parse_str, SP_TOKEN_FUNCTION, &(df->function)},
+ {parse_regexp, SP_TOKEN_FUNCTION_REGEXP, &(df->r_function)},
+ {parse_str, SP_TOKEN_DUMP, &(df->dump)},
+ {parse_empty, SP_TOKEN_ALLOW, &(df->allow)},
+ {parse_empty, SP_TOKEN_DROP, &(df->drop)},
+ {parse_str, SP_TOKEN_HASH, &(df->hash)},
+ {parse_str, SP_TOKEN_PARAM, &(df->param)},
+ {parse_regexp, SP_TOKEN_VALUE_REGEXP, &(df->regexp)},
+ {parse_str, SP_TOKEN_VALUE, &(df->value)},
+ {parse_regexp, SP_TOKEN_PARAM_REGEXP, &(df->r_param)},
+ {parse_php_type, SP_TOKEN_PARAM_TYPE, &(df->param_type)},
+ {parse_str, SP_TOKEN_RET, &(df->ret)},
+ {parse_cidr, SP_TOKEN_CIDR, &(df->cidr)},
+ {parse_regexp, SP_TOKEN_RET_REGEXP, &(df->r_ret)},
+ {parse_php_type, SP_TOKEN_RET_TYPE, &(df->ret_type)},
+ {parse_str, SP_TOKEN_LOCAL_VAR, &(df->var)},
+ {0}};
+
+ ret = parse_keywords(sp_config_funcs_disabled_functions, line);
+
+ if (0 != ret) {
+ return ret;
+ }
+
+ if (true == disable){
+ df->enable = false;
+ } else {
+ df->enable = true;
+ }
+
+ if (df->value && df->regexp) {
+ sp_log_err("config",
+ "Invalid configuration line: 'sp.disabled_functions%s':"
+ "'.value' and '.regexp' are mutually exclusives.",
+ line);
+ return -1;
+ } else if (df->r_function && df->function) {
+ sp_log_err("config",
+ "Invalid configuration line: 'sp.disabled_functions%s': "
+ "'.r_function' and '.function' are mutually exclusive.",
+ line);
+ return -1;
+ } else if (df->r_filename && df->filename) {
+ sp_log_err("config",
+ "Invalid configuration line: 'sp.disabled_functions%s':"
+ "'.r_filename' and '.filename' are mutually exclusive.",
+ line);
+ return -1;
+ } else if (df->r_param && df->param) {
+ sp_log_err("config",
+ "Invalid configuration line: 'sp.disabled_functions%s':"
+ "'.r_param' and '.param' are mutually exclusive.",
+ line);
+ return -1;
+ } else if (df->r_ret && df->ret) {
+ sp_log_err("config",
+ "Invalid configuration line: 'sp.disabled_functions%s':"
+ "'.r_ret' and '.ret' are mutually exclusive.",
+ line);
+ return -1;
+ } else if ((df->r_ret || df->ret) && (df->r_param || df->param)) {
+ sp_log_err("config",
+ "Invalid configuration line: 'sp.disabled_functions%s':"
+ "`ret` and `param` are mutually exclusives.",
+ line);
+ return -1;
+ } else if (!(df->r_function || df->function)) {
+ sp_log_err("config",
+ "Invalid configuration line: 'sp.disabled_functions%s':"
+ " must take a function name.",
+ line);
+ return -1;
+ } else if (!(df->allow ^ df->drop)) {
+ sp_log_err("config",
+ "Invalid configuration line: 'sp.disabled_functions%s': The "
+ "rule must either be a `drop` or and `allow` one.",
+ line);
+ return -1;
+ }
+
+ if (df->param && strchr(df->param, '[')) { // assume that this is an array
+ df->param_array_keys = sp_new_list();
+ if (0 != array_to_list(&df->param, &df->param_array_keys)) {
+ pefree(df->param_array_keys, 1);
+ return -1;
+ }
+ df->param_is_array = 1;
+ }
+
+ if (df->var && strchr(df->var, '[')) { // assume that this is an array
+ df->var_array_keys = sp_new_list();
+ if (0 != array_to_list(&df->var, &df->var_array_keys)) {
+ pefree(df->var_array_keys, 1);
+ return -1;
+ }
+ df->var_is_array = 1;
+ }
+
+ bool match = false;
+ const char *key[4] = {"include", "include_once", "require", "require_once"};
+ for (size_t i = 0; i < 4; i++) {
+ if (df->r_function && true == is_regexp_matching(df->r_function, key[i])) {
+ match = true;
+ break;
+ } else if (df->function && 0 == strcmp(df->function, key[i])) {
+ match = true;
+ break;
+ }
+ }
+ if (true == match && df->regexp) {
+ sp_list_insert(
+ SNUFFLEUPAGUS_G(config).config_regexp_inclusion->regexp_inclusion,
+ df->regexp);
+ } else if (df->ret || df->r_ret || df->ret_type) {
+ sp_list_insert(
+ SNUFFLEUPAGUS_G(config).config_disabled_functions_ret->disabled_functions,
+ df);
+ } else {
+ sp_list_insert(
+ SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions,
+ df);
+ }
+ return ret;
+}
+
+int parse_upload_validation(char *line) {
+ bool disable = false, enable = false;
+ sp_config_functions sp_config_funcs_upload_validation[] = {
+ {parse_str, SP_TOKEN_UPLOAD_SCRIPT,
+ &(SNUFFLEUPAGUS_G(config).config_upload_validation->script)},
+ {parse_empty, SP_TOKEN_SIMULATION,
+ &(SNUFFLEUPAGUS_G(config).config_upload_validation->simulation)},
+ {parse_empty, SP_TOKEN_ENABLE, &(enable)},
+ {parse_empty, SP_TOKEN_DISABLE, &(disable)},
+ {0}};
+
+ int ret = parse_keywords(sp_config_funcs_upload_validation, line);
+
+ if (0 != ret) {
+ return ret;
+ }
+
+ if (!(enable ^ disable)) {
+ sp_log_err("config", "A rule can't be enabled and disabled.");
+ return -1;
+ }
+ SNUFFLEUPAGUS_G(config).config_upload_validation->enable = enable;
+
+ char const *script = SNUFFLEUPAGUS_G(config).config_upload_validation->script;
+
+ if (!script) {
+ sp_log_err("config", "The `script` directive is mandatory in %s",
+ line);
+ return -1;
+ } else if (-1 == access(script, F_OK)) {
+ sp_log_err("config", "The `script` (%s) doesn't exist.", script);
+ return -1;
+ } else if (-1 == access(script, X_OK)) {
+ sp_log_err("config", "The `script` (%s) isn't executable.", script);
+ return -1;
+ }
+
+ return ret;
+}
diff --git a/src/sp_config_keywords.h b/src/sp_config_keywords.h
new file mode 100644
index 00000000..40fac47e
--- /dev/null
+++ b/src/sp_config_keywords.h
@@ -0,0 +1,16 @@
+#ifndef SP_CONFIG_KEYWORDS_H
+#define SP_CONFIG_KEYWORDS_H
+#include "php_snuffleupagus.h"
+
+int parse_random(char *line);
+int parse_disable_xxe(char *line);
+int parse_auto_cookie_secure(char *line);
+int parse_global_strict(char *line);
+int parse_global(char *line) ;
+int parse_cookie_encryption(char *line);
+int parse_unserialize(char *line) ;
+int parse_readonly_exec(char *line);
+int parse_disabled_functions(char *line) ;
+int parse_upload_validation(char *line);
+
+#endif // __SP_CONFIG_KEYWORDS_H
\ No newline at end of file
diff --git a/src/sp_config_utils.c b/src/sp_config_utils.c
new file mode 100644
index 00000000..e05e95e0
--- /dev/null
+++ b/src/sp_config_utils.c
@@ -0,0 +1,211 @@
+#include "php_snuffleupagus.h"
+
+static int validate_int(const char *value);
+static int validate_str(const char *value);
+
+static sp_pure int validate_int(const char *value) {
+ for (size_t i = 0; i < strlen(value); i++) {
+ if (!isdigit(value[i])) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static sp_pure int validate_str(const char *value) {
+ int balance = 0; // ghetto [] validation
+
+ if (!strchr(value, '[')) {
+ return 0;
+ }
+
+ for (size_t i = 0; i < strlen(value); i++) {
+ if (value[i] == '[') {
+ balance++;
+ } else if (value[i] == ']') {
+ balance--;
+ }
+ if (balance < 0) {
+ return -1;
+ }
+ }
+ return balance != 0;
+}
+
+int parse_keywords(sp_config_functions *funcs, char *line) {
+ int value_len = 0;
+ const char *original_line = line;
+ for (size_t i = 0; funcs[i].func; i++) {
+ if (!strncmp(funcs[i].token, line, strlen(funcs[i].token))) {
+ line += strlen(funcs[i].token);
+ value_len = funcs[i].func(line, funcs[i].token, funcs[i].retval) + 1;
+ if (value_len == 0) { // bad parameter
+ return -1;
+ }
+ line += value_len;
+ i = -1; // we start the loop again
+ }
+ }
+ while (*line == ';' || *line == '\t' || *line == ' ') {
+ line++;
+ }
+
+ if (*line == '#') {
+ return 0;
+ }
+
+ if (*line) {
+ sp_log_err("config", "Trailing chars '%s' at the end of '%s'.", line,
+ original_line);
+ return -1;
+ }
+ return 0;
+}
+
+static char *get_string(size_t *consumed, char *restrict line,
+ const char *restrict keyword) {
+ enum { IN_ESCAPE, NONE } state = NONE;
+ char *original_line = line;
+ size_t j = 0;
+
+ char *ret = NULL;
+ if (NULL == line) {
+ goto err;
+ }
+
+ ret = pecalloc(sizeof(char), strlen(original_line) + 1, 1);
+
+ /* The first char of a string is always '"', since they MUST be quoted. */
+ if ('"' == *line) {
+ line++;
+ } else {
+ goto err;
+ }
+
+ for (size_t i = 0; line[i] && j < strlen(original_line) - 2; i++) {
+ switch (line[i]) {
+ case '"':
+ /* A double quote at this point is either:
+ - at the very end of the string.
+ - escaped
+ */
+ if ((state == NONE) && (line[i + 1] == SP_TOKEN_END_PARAM)) {
+ /* The `+2` if for
+ 1. the terminal double-quote
+ 2. the SP_TOKEN_END_PARAM
+ */
+ *consumed = i + 2;
+ return ret;
+ } else if (state == IN_ESCAPE) {
+ break; // we're on an escped double quote
+ } else {
+ goto err;
+ }
+ case '\\':
+ if (state == NONE) {
+ state = IN_ESCAPE;
+ continue;
+ }
+ default:
+ break;
+ }
+ if (state == IN_ESCAPE) {
+ state = NONE;
+ }
+ ret[j++] = line[i];
+ }
+err:
+ sp_log_err("error",
+ "There is an issue with the parsing of '%s': it doesn't look like a valid string.",
+ original_line ? original_line : "NULL");
+ line = NULL;
+ return NULL;
+}
+
+static char *get_misc(char *restrict line, const char *restrict keyword) {
+ size_t i = 0;
+ char *ret = pecalloc(sizeof(char), 1024, 1);
+
+ while (i < 1024 - 1 && line[i] && line[i] != SP_TOKEN_END_PARAM) {
+ ret[i] = line[i];
+ i++;
+ }
+
+ if (line[i] != SP_TOKEN_END_PARAM) {
+ if (i >= 1024 - 1) {
+ sp_log_err("config", "The following line is too long: %s.", line);
+ } else {
+ sp_log_err("config", "Missing closing %c in line %s.", SP_TOKEN_END_PARAM,
+ line);
+ }
+ return NULL;
+ } else if (i == 0) {
+ sp_log_err("config", "The keyword %s%c is expecting a parameter.",
+ keyword, SP_TOKEN_END_PARAM);
+ return NULL;
+ }
+ return ret;
+}
+
+char *get_param(size_t *consumed, char *restrict line, sp_type type,
+ const char *restrict keyword) {
+ char *retval = NULL;
+ if (type == SP_TYPE_STR) {
+ retval = get_string(consumed, line, keyword);
+ } else {
+ retval = get_misc(line, keyword);
+ *consumed = retval ? strlen(retval) : 0;
+ }
+
+ if (retval) {
+ if (type == SP_TYPE_STR && 0 == validate_str(retval)) {
+ return retval;
+ } else if (type == SP_TYPE_INT && 0 == validate_int(retval)) {
+ return retval;
+ }
+ }
+ return NULL;
+}
+
+// FIXME this is leaking like hell @blotus
+int array_to_list(char **name_ptr, sp_node_t **keys) {
+ int in_key = 0;
+ size_t i = 0;
+ char *name = *name_ptr;
+ char *key_name = ecalloc(strlen(name) + 1, 1); // im way too lazy for
+ // now
+ char *tmp = ecalloc(strlen(name) + 1, 1);
+
+ for (i = 0; name[i] != '['; i++) {
+ tmp[i] = name[i];
+ }
+ tmp[i] = 0;
+
+ for (size_t j = 0; name[i]; i++) {
+ const char c = name[i];
+ if (c == '[') {
+ if (in_key == 0) {
+ in_key = 1;
+ } else {
+ efree(key_name);
+ return -1;
+ }
+ } else if (c == ']') {
+ if (in_key == 0) {
+ efree(key_name);
+ return -1;
+ } else {
+ in_key = 0;
+ j = 0;
+ sp_list_insert(*keys, pestrdup(key_name, 1));
+ memset(key_name, 0, strlen(name) + 1);
+ }
+ } else if (in_key == 1) {
+ key_name[j] = c;
+ j++;
+ }
+ }
+ efree(key_name);
+ *name_ptr = pestrdup(tmp, 1);
+ return in_key;
+}
diff --git a/src/sp_config_utils.h b/src/sp_config_utils.h
new file mode 100644
index 00000000..f2f8fce2
--- /dev/null
+++ b/src/sp_config_utils.h
@@ -0,0 +1,8 @@
+#ifndef SP_CONFIG_UTILS
+#define SP_CONFIG_UTILS
+
+int parse_keywords(sp_config_functions *, char *);
+char *get_param(size_t *, char *restrict, sp_type, const char *restrict);
+int array_to_list(char **, sp_node_t **);
+
+#endif /* SP_CONFIG_UTILS */
diff --git a/src/sp_cookie_encryption.c b/src/sp_cookie_encryption.c
new file mode 100644
index 00000000..5248486f
--- /dev/null
+++ b/src/sp_cookie_encryption.c
@@ -0,0 +1,216 @@
+#include "php_snuffleupagus.h"
+
+#include "ext/standard/url.h"
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
+
+static unsigned int nonce_d = 0;
+
+static inline void generate_key(unsigned char *key) {
+ PHP_SHA256_CTX ctx;
+ const char *user_agent = sp_getenv("HTTP_USER_AGENT");
+ const char *remote_addr = sp_getenv("REMOTE_ADDR");
+ const char *encryption_key =
+ SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key;
+
+ /* 32 is the size of a SHA256. */
+ assert(32 == crypto_secretbox_KEYBYTES);
+
+ PHP_SHA256Init(&ctx);
+
+ if (user_agent) {
+ PHP_SHA256Update(&ctx, (unsigned char *)user_agent, strlen(user_agent));
+ }
+
+ if (remote_addr) {
+ char out[128];
+ apply_mask_on_ip(out, remote_addr);
+ PHP_SHA256Update(&ctx, (unsigned char*)out, sizeof(out));
+ }
+
+ if (encryption_key) {
+ PHP_SHA256Update(&ctx, (const unsigned char *)encryption_key,
+ strlen(encryption_key));
+ }
+
+ PHP_SHA256Final((unsigned char *)key, &ctx);
+}
+
+int decrypt_cookie(zval *pDest, int num_args, va_list args,
+ zend_hash_key *hash_key) {
+ unsigned char key[crypto_secretbox_KEYBYTES] = {0};
+ size_t value_len;
+ zend_string *debase64;
+ unsigned char *decrypted;
+ int ret = 0;
+
+ /* If the cookie isn't in the conf, it shouldn't be encrypted. */
+ if (0 ==
+ zend_hash_exists(SNUFFLEUPAGUS_G(config).config_cookie_encryption->names,
+ hash_key->key)) {
+ return ZEND_HASH_APPLY_KEEP;
+ }
+
+ generate_key(key);
+
+ value_len = php_url_decode(Z_STRVAL_P(pDest), Z_STRLEN_P(pDest));
+
+ if (value_len == 0) {
+ return ZEND_HASH_APPLY_KEEP;
+ }
+
+ debase64 = php_base64_decode((unsigned char *)(Z_STRVAL_P(pDest)), value_len);
+
+ if (value_len <
+ crypto_secretbox_NONCEBYTES + crypto_secretbox_ZEROBYTES) {
+ sp_log_msg("cookie_encryption", LOG_DROP,
+ "Buffer underflow tentative detected in cookie encryption handling.");
+ return ZEND_HASH_APPLY_REMOVE;
+ }
+
+ decrypted = pecalloc(value_len, 1, 0);
+
+ ret = crypto_secretbox_open(
+ decrypted,
+ (unsigned char *)(ZSTR_VAL(debase64) + crypto_secretbox_NONCEBYTES),
+ ZSTR_LEN(debase64) - crypto_secretbox_NONCEBYTES,
+ (unsigned char *)ZSTR_VAL(debase64), key);
+
+ if (ret == -1) {
+ sp_log_msg("cookie_encryption", LOG_DROP,
+ "Something went wrong with the decryption of %s.",
+ ZSTR_VAL(hash_key->key));
+ return ZEND_HASH_APPLY_REMOVE;
+ }
+
+ ZVAL_STRINGL(pDest, (char *)(decrypted + crypto_secretbox_ZEROBYTES),
+ ZSTR_LEN(debase64) - crypto_secretbox_NONCEBYTES - 1 -
+ crypto_secretbox_ZEROBYTES);
+
+ return ZEND_HASH_APPLY_KEEP;
+}
+
+/**
+ This function will return the `data` of length `data_len` encrypted in the
+ form
+ base64(nonce | encrypted_data) (with `|` being the concatenation
+ operation).
+
+ The `nonce` is time-based.
+*/
+static zend_string *encrypt_data(char *data, unsigned long long data_len) {
+ const size_t encrypted_msg_len = crypto_secretbox_ZEROBYTES + data_len + 1;
+ const size_t emsg_and_nonce_len = encrypted_msg_len + crypto_secretbox_NONCEBYTES;
+
+ unsigned char key[crypto_secretbox_KEYBYTES] = {0};
+ unsigned char nonce[crypto_secretbox_NONCEBYTES] = {0};
+ unsigned char *data_to_encrypt = pecalloc(encrypted_msg_len, 1, 0);
+ unsigned char *encrypted_data = pecalloc(emsg_and_nonce_len, 1, 1);
+
+ generate_key(key);
+
+ /* tweetnacl's API requires the message to be padded with
+ crypto_secretbox_ZEROBYTES zeroes. */
+ memcpy(data_to_encrypt + crypto_secretbox_ZEROBYTES, data, data_len);
+
+ assert(sizeof(size_t) <= crypto_secretbox_NONCEBYTES);
+
+ nonce_d++;
+ sscanf((char*)nonce, "%ud", &nonce_d);
+
+ memcpy(encrypted_data, nonce, crypto_secretbox_NONCEBYTES);
+ crypto_secretbox(encrypted_data + crypto_secretbox_NONCEBYTES,
+ data_to_encrypt, encrypted_msg_len, nonce, key);
+
+ zend_string *z = php_base64_encode(encrypted_data, emsg_and_nonce_len);
+ sp_log_debug("cookie_encryption", "Cookie value:%s:", z->val);
+ return z;
+}
+
+PHP_FUNCTION(sp_setcookie) {
+ zval params[7] = { 0 };
+ zend_string *name = NULL, *value = NULL, *path = NULL, *domain = NULL;
+ zend_long expires = 0;
+ zend_bool secure = 0, httponly = 0;
+ zval ret_val;
+ zval func_name;
+
+ ZEND_PARSE_PARAMETERS_START(1, 7)
+ Z_PARAM_STR(name)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_STR(value)
+ Z_PARAM_LONG(expires)
+ Z_PARAM_STR(path)
+ Z_PARAM_STR(domain)
+ Z_PARAM_BOOL(secure)
+ Z_PARAM_BOOL(httponly)
+ ZEND_PARSE_PARAMETERS_END();
+
+ /* If the request was issued over HTTPS, the cookie should be "secure" */
+ if (SNUFFLEUPAGUS_G(config).config_auto_cookie_secure) {
+ const zval server_vars = PG(http_globals)[TRACK_VARS_SERVER];
+ if (Z_TYPE(server_vars) == IS_ARRAY) {
+ const zval *is_https =
+ zend_hash_str_find(Z_ARRVAL(server_vars), "HTTPS", strlen("HTTPS"));
+ if (NULL != is_https) {
+ secure = 1;
+ }
+ }
+ }
+
+ /* If the cookie's value is encrypted, it won't be usable by
+ * javascript anyway.
+ */
+ if (zend_hash_exists(SNUFFLEUPAGUS_G(config).config_cookie_encryption->names,
+ name) > 0) {
+ httponly = 1;
+ }
+
+ /* Shall we encrypt the cookie's value? */
+ if (zend_hash_exists(SNUFFLEUPAGUS_G(config).config_cookie_encryption->names,
+ name) > 0 && value) {
+ zend_string *encrypted_data = encrypt_data(value->val, value->len);
+ ZVAL_STR_COPY(¶ms[1], encrypted_data);
+ zend_string_release(encrypted_data);
+ } else if (value) {
+ ZVAL_STR_COPY(¶ms[1], value);
+ }
+
+ ZVAL_STRING(&func_name, "setcookie");
+ ZVAL_STR_COPY(¶ms[0], name);
+ ZVAL_LONG(¶ms[2], expires);
+ if (path) {
+ ZVAL_STR_COPY(¶ms[3], path);
+ }
+ if (domain) {
+ ZVAL_STR_COPY(¶ms[4], domain);
+ }
+ if (secure) {
+ ZVAL_LONG(¶ms[5], secure);
+ }
+ if (httponly) {
+ ZVAL_LONG(¶ms[6], httponly);
+ }
+
+ /* This is the _fun_ part: because PHP is utterly idiotic and nonsensical,
+ the `call_user_function` macro will __discard__ (yes) its first argument
+ (the hashtable), effectively calling functions from `CG(function_table)`.
+ This is why were replacing our hook with the original function, calling
+ the function, and then re-hooking it. */
+ void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
+ handler = zend_hash_str_find_ptr(SNUFFLEUPAGUS_G(sp_internal_functions_hook), "setcookie",
+ strlen("setcookie"));
+ zend_internal_function *func = zend_hash_str_find_ptr(
+ CG(function_table), "setcookie", strlen("setcookie"));
+ func->handler = handler;
+
+ call_user_function(CG(function_table), NULL, &func_name, &ret_val, 7, params);
+
+ func->handler = PHP_FN(sp_setcookie);
+}
+
+int hook_cookies() {
+ HOOK_FUNCTION("setcookie", sp_internal_functions_hook, PHP_FN(sp_setcookie), false);
+
+ return SUCCESS;
+}
diff --git a/src/sp_cookie_encryption.h b/src/sp_cookie_encryption.h
new file mode 100644
index 00000000..9904738d
--- /dev/null
+++ b/src/sp_cookie_encryption.h
@@ -0,0 +1,17 @@
+
+#ifndef __SP_COOKIE_ENCRYPTION
+#define __SP_COOKIE_ENCRYPTION
+
+#include "SAPI.h"
+#include "tweetnacl.h"
+
+#include "sp_utils.h"
+
+#include "ext/hash/php_hash.h"
+#include "ext/hash/php_hash_sha.h"
+#include "ext/standard/base64.h"
+
+int hook_cookies();
+int decrypt_cookie(zval *pDest, int num_args, va_list args, zend_hash_key *hash_key);
+
+#endif /* __SP_COOKIE_ENCRYPTION */
diff --git a/src/sp_disable_xxe.c b/src/sp_disable_xxe.c
new file mode 100644
index 00000000..d11b3d03
--- /dev/null
+++ b/src/sp_disable_xxe.c
@@ -0,0 +1,25 @@
+#include "php_snuffleupagus.h"
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
+
+PHP_FUNCTION(sp_libxml_disable_entity_loader) { RETURN_TRUE; }
+
+int hook_libxml_disable_entity_loader() {
+ zval func_name;
+ zval hmac;
+ zval params[1];
+
+ TSRMLS_FETCH();
+
+ /* Call the php function here instead of re-implementing it is a bit
+ * ugly, but we do not want to introduce compile-time dependencies against
+ * libxml. */
+ ZVAL_STRING(&func_name, "libxml_disable_entity_loader");
+ ZVAL_STRING(¶ms[0], "true");
+ call_user_function(CG(function_table), NULL, &func_name, &hmac, 1, params);
+
+ HOOK_FUNCTION("libxml_disable_entity_loader", sp_internal_functions_hook,
+ PHP_FN(sp_libxml_disable_entity_loader), false);
+
+ return SUCCESS;
+}
diff --git a/src/sp_disable_xxe.h b/src/sp_disable_xxe.h
new file mode 100644
index 00000000..274c169b
--- /dev/null
+++ b/src/sp_disable_xxe.h
@@ -0,0 +1,6 @@
+#ifndef __SP_DISABLE_XXE_H
+#define __SP_DISABLE_XXE_H
+
+int hook_libxml_disable_entity_loader();
+
+#endif /* __SP_DISABLE_XXE_H */
diff --git a/src/sp_disabled_functions.c b/src/sp_disabled_functions.c
new file mode 100644
index 00000000..55d782b5
--- /dev/null
+++ b/src/sp_disabled_functions.c
@@ -0,0 +1,356 @@
+#include "php_snuffleupagus.h"
+
+#include "zend_execute.h"
+#include "zend_hash.h"
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus);
+
+ZEND_COLD static zend_always_inline bool is_hash_matching(
+ const char* current_filename,
+ sp_disabled_function const* const config_node) {
+ char current_file_hash[SHA256_SIZE * 2];
+ compute_hash(current_filename, current_file_hash);
+ return (0 == strncmp(current_file_hash, config_node->hash, SHA256_SIZE));
+}
+
+static zend_always_inline char* get_complete_function_path(
+ zend_execute_data const* const execute_data) {
+ char const* class_name;
+ char const* const function_name =
+ ZSTR_VAL(execute_data->func->common.function_name);
+ char* complete_path_function = NULL;
+
+ class_name = get_active_class_name(NULL);
+ if (*class_name) {
+ const size_t len = strlen(class_name) + 2 + strlen(function_name) + 1;
+ complete_path_function = emalloc(len);
+ snprintf(complete_path_function, len, "%s::%s", class_name, function_name);
+ } else {
+ complete_path_function = estrdup(function_name);
+ }
+ return complete_path_function;
+}
+
+static bool is_local_var_matching(zend_execute_data *execute_data, const sp_disabled_function *const config_node) {
+ zend_execute_data *orig_execute_data = execute_data;
+
+ /*because execute_data points to hooked function data,
+ which we dont care about */
+ zend_execute_data *current = execute_data->prev_execute_data;
+ zval *value = NULL;
+
+ while (current) {
+ zend_string *key = NULL;
+ EG(current_execute_data) = current;
+ zend_array *symtable = zend_rebuild_symbol_table();
+ ZEND_HASH_FOREACH_STR_KEY_VAL(symtable, key, value) {
+ if (0 == strcmp(config_node->var, key->val)) { // is the var name right?
+ if (Z_TYPE_P(value) == IS_INDIRECT) {
+ value = Z_INDIRECT_P(value);
+ }
+ if (Z_TYPE_P(value) != IS_ARRAY) {
+ char *var_value_str = sp_convert_to_string(value);
+ if (true == sp_match_value(var_value_str, config_node->value, config_node->regexp)) {
+ efree(var_value_str);
+ EG(current_execute_data) = orig_execute_data;
+ return true;
+ }
+ efree(var_value_str);
+ }
+ else {
+ EG(current_execute_data) = orig_execute_data;
+ return sp_match_array_key_recurse(value, config_node->var_array_keys, config_node->value, NULL);
+ }
+ }
+ }
+ ZEND_HASH_FOREACH_END();
+ current = current->prev_execute_data;
+ }
+
+ EG(current_execute_data) = orig_execute_data;
+ return false;
+}
+
+bool should_disable(zend_execute_data* execute_data) {
+ const char* current_filename = zend_get_executed_filename(TSRMLS_C);
+ const sp_node_t* config =
+ SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions;
+ const char* function_name =
+ ZSTR_VAL(execute_data->func->common.function_name);
+ char* complete_path_function;
+ char const* client_ip = sp_getenv("REMOTE_ADDR");
+
+ if (!function_name) {
+ return false;
+ }
+
+ if (!config || !config->data) {
+ return false;
+ }
+
+ complete_path_function = get_complete_function_path(execute_data);
+
+ while (config) {
+ sp_disabled_function const* const config_node =
+ (sp_disabled_function*)(config->data);
+ const char* arg_name = NULL;
+ const char* arg_value_str = NULL;
+
+ if (false == config_node->enable) {
+ goto next;
+ }
+
+ if (config_node->function) { /* Litteral match against the function name. */
+ if (0 != strcmp(config_node->function, complete_path_function)) {
+ goto next;
+ }
+ } else if (config_node->r_function) {
+ if (false ==
+ is_regexp_matching(config_node->r_function, complete_path_function)) {
+ goto next;
+ }
+ }
+ if (config_node->var) {
+ if (false == is_local_var_matching(execute_data, config_node)) {
+ goto next;
+ }
+ }
+
+ if (config_node->filename) { /* Check the current file name. */
+ if (0 != strcmp(current_filename, config_node->filename)) {
+ goto next;
+ }
+ } else if (config_node->r_filename) {
+ if (false ==
+ is_regexp_matching(config_node->r_filename, current_filename)) {
+ goto next;
+ }
+ }
+
+ if (config_node->hash) {
+ if (false == is_hash_matching(current_filename, config_node)) {
+ goto next;
+ }
+ }
+
+ if (client_ip && config_node->cidr &&
+ (false == cidr_match(client_ip, config_node->cidr))) {
+ goto next;
+ }
+
+ /* Check if we filter on parameter value*/
+ if (config_node->param || config_node->r_param) {
+ const unsigned int nb_param = execute_data->func->common.num_args;
+ bool arg_matched = false;
+
+ for (unsigned int i = 0; i < nb_param; i++) {
+ arg_matched = false;
+ if (ZEND_USER_CODE(execute_data->func->type)) { // yay consistency
+ arg_name = ZSTR_VAL(execute_data->func->common.arg_info[i].name);
+ } else {
+ arg_name = execute_data->func->internal_function.arg_info[i].name;
+ }
+
+ const bool arg_matching =
+ config_node->param && (0 == strcmp(arg_name, config_node->param));
+ const bool pcre_matching =
+ config_node->r_param &&
+ (true == is_regexp_matching(config_node->r_param, arg_name));
+
+ /* This is the parameter name we're looking for. */
+ if (true == arg_matching || true == pcre_matching) {
+ zval* arg_value = ZEND_CALL_VAR_NUM(execute_data, i);
+
+ if (config_node->param_type) { // Are we matching on the `type`?
+ if (config_node->param_type == Z_TYPE_P(arg_value)) {
+ arg_matched = true;
+ break;
+ }
+ } else if (Z_TYPE_P(arg_value) == IS_ARRAY) {
+ arg_value_str = estrdup("Array");
+ // match on arr -> match on all key content, if a key is an array,
+ // ignore it
+ // match on arr[foo] -> match only on key foo, if the key is an
+ // array, match on all keys content
+ if (config_node->param_is_array == true) {
+ if (true == sp_match_array_key_recurse(
+ arg_value, config_node->param_array_keys,
+ config_node->value, config_node->regexp)) {
+ arg_matched = true;
+ break;
+ }
+ } else { // match on all keys, but don't go into subarray
+ if (true == sp_match_array_key(arg_value, config_node->value,
+ config_node->regexp)) {
+ arg_matched = true;
+ break;
+ }
+ }
+ } else {
+ arg_value_str = sp_convert_to_string(arg_value);
+ if (true == sp_match_value(arg_value_str, config_node->value,
+ config_node->regexp)) {
+ arg_matched = true;
+ break;
+ }
+ }
+ }
+ }
+ if (false == arg_matched) {
+ goto next;
+ }
+ }
+
+ /* Everything matched.*/
+
+ if (true == config_node->allow) {
+ goto allow;
+ }
+
+ sp_log_disable(complete_path_function, arg_name, arg_value_str,
+ config_node);
+ if (true == config_node->simulation) {
+ goto next;
+ } else { // We've got a match, the function won't be executed
+ efree(complete_path_function);
+ return true;
+ }
+next:
+config = config->next;
+ }
+allow:
+ efree(complete_path_function);
+ return false;
+}
+
+static bool should_drop_on_ret(zval* return_value,
+ const zend_execute_data* const execute_data) {
+ const sp_node_t* config =
+ SNUFFLEUPAGUS_G(config).config_disabled_functions_ret->disabled_functions;
+ char* complete_path_function = get_complete_function_path(execute_data);
+ const char* current_filename = zend_get_executed_filename(TSRMLS_C);
+
+ if (!config || !config->data) {
+ return false;
+ }
+
+ while (config) {
+ char* ret_value_str = NULL;
+ sp_disabled_function const* const config_node =
+ (sp_disabled_function*)(config->data);
+
+ if (false == config_node->enable) {
+ goto next;
+ }
+
+ if (config_node->function) {
+ if (0 != strcmp(config_node->function, complete_path_function)) {
+ goto next;
+ }
+ } else if (config_node->r_function) {
+ if (false ==
+ is_regexp_matching(config_node->r_function, complete_path_function)) {
+ goto next;
+ }
+ }
+
+ if (config_node->filename) { /* Check the current file name. */
+ if (0 != strcmp(current_filename, config_node->filename)) {
+ goto next;
+ }
+ } else if (config_node->r_filename) {
+ if (false ==
+ is_regexp_matching(config_node->r_filename, current_filename)) {
+ goto next;
+ }
+ }
+
+ if (config_node->hash) {
+ if (false == is_hash_matching(current_filename, config_node)) {
+ goto next;
+ }
+ }
+
+ ret_value_str = sp_convert_to_string(return_value); // FIXME memleak
+
+ bool match_type = (config_node->ret_type) &&
+ (config_node->ret_type == Z_TYPE_P(return_value));
+ bool match_value = (config_node->ret || config_node->r_ret) &&
+ (true == sp_match_value(ret_value_str, config_node->ret,
+ config_node->r_ret));
+
+ if (true == match_type || match_value) {
+ if (true == config_node->allow) {
+ efree(complete_path_function);
+ return false;
+ }
+ sp_log_disable_ret(complete_path_function, ret_value_str, config_node);
+ if (false == config_node->simulation) {
+ efree(complete_path_function);
+ return true;
+ }
+ }
+ next:
+ config = config->next;
+ }
+ efree(complete_path_function);
+ return false;
+}
+
+ZEND_FUNCTION(check_disabled_function) {
+ void (*orig_handler)(INTERNAL_FUNCTION_PARAMETERS);
+ const char* current_function_name = get_active_function_name(TSRMLS_C);
+
+ if (true == should_disable(execute_data)) {
+ return;
+ }
+
+ if ((orig_handler = zend_hash_str_find_ptr(
+ SNUFFLEUPAGUS_G(disabled_functions_hook), current_function_name,
+ strlen(current_function_name)))) {
+ orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+ if (true == should_drop_on_ret(return_value, execute_data)) {
+ zend_bailout();
+ }
+ } else {
+ sp_log_err(
+ "disabled_functions",
+ "Unable to find the pointer to the original function '%s' in the "
+ "hashtable.\n",
+ current_function_name);
+ }
+}
+
+static int hook_functions(const sp_node_t* config) {
+ while (config && config->data) {
+ const char* function_name = ((sp_disabled_function*)config->data)->function;
+ const pcre* function_name_regexp =
+ ((sp_disabled_function*)config->data)->r_function;
+
+ if (NULL != function_name) { // hook function by name
+ HOOK_FUNCTION(function_name, disabled_functions_hook,
+ PHP_FN(check_disabled_function), false);
+ } else if (NULL != function_name_regexp) { // hook function by regexp
+ HOOK_FUNCTION_BY_REGEXP(function_name_regexp, disabled_functions_hook,
+ PHP_FN(check_disabled_function), false);
+ } else {
+ return FAILURE;
+ }
+
+ config = config->next;
+ }
+ return SUCCESS;
+}
+
+int hook_disabled_functions(void) {
+ TSRMLS_FETCH();
+
+ int ret = SUCCESS;
+
+ ret |= hook_functions(
+ SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions);
+ ret |= hook_functions(SNUFFLEUPAGUS_G(config)
+ .config_disabled_functions_ret->disabled_functions);
+
+ return ret;
+}
diff --git a/src/sp_disabled_functions.h b/src/sp_disabled_functions.h
new file mode 100644
index 00000000..680eabb4
--- /dev/null
+++ b/src/sp_disabled_functions.h
@@ -0,0 +1,11 @@
+#ifndef __SP_DISABLE_FUNCTIONS_H
+#define __SP_DISABLE_FUNCTIONS_H
+
+#include "ext/hash/php_hash.h"
+#include "ext/hash/php_hash_sha.h"
+#include "ext/standard/md5.h"
+
+int hook_disabled_functions();
+bool should_disable(zend_execute_data* function_name);
+
+#endif /* __SP_DISABLE_FUNCTIONS_H */
diff --git a/src/sp_execute.c b/src/sp_execute.c
new file mode 100644
index 00000000..faf126c8
--- /dev/null
+++ b/src/sp_execute.c
@@ -0,0 +1,100 @@
+#include "php_snuffleupagus.h"
+
+#include
+#include
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus);
+
+static void (*orig_execute_ex)(zend_execute_data *execute_data);
+static int (*orig_zend_stream_open)(const char *filename,
+ zend_file_handle *handle);
+
+// FIXME handle symlink
+ZEND_COLD static inline void terminate_if_writable(const char *filename) {
+ if (0 == access(filename, W_OK)) {
+ if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->simulation) {
+ sp_log_msg("readonly_exec", LOG_NOTICE,
+ "Attempted execution of a writable file (%s).", filename);
+ } else {
+ sp_log_msg("readonly_exec", LOG_DROP,
+ "Attempted execution of a writable file (%s).", filename);
+ sp_terminate();
+ }
+ } else {
+ if (EACCES != errno) {
+ sp_log_err("Writable execution", "Error while accessing %s: %s", filename,
+ strerror(errno));
+ }
+ }
+}
+
+static void check_inclusion_regexp(const char * const filename) {
+ if (SNUFFLEUPAGUS_G(config).config_regexp_inclusion->regexp_inclusion) {
+ const sp_node_t* config = SNUFFLEUPAGUS_G(config).config_regexp_inclusion->regexp_inclusion;
+ if (!config || !config->data) {
+ return;
+ }
+ while (config) {
+ pcre *config_node = (pcre*)(config->data);
+ if (false == is_regexp_matching(config_node, filename)) {
+ sp_log_msg("include", LOG_DROP, "Inclusion of a forbidden file (%s).", filename);
+ sp_terminate();
+ }
+ config = config->next;
+ }
+ }
+}
+
+static void sp_execute_ex(zend_execute_data *execute_data) {
+ if (NULL == execute_data->func->common.function_name) {
+ goto execute;
+ }
+
+ if (true == should_disable(execute_data)) {
+ return;
+ }
+
+ if (execute_data->func->op_array.type == ZEND_EVAL_CODE) {
+ sp_log_debug("Currently in an eval\n");
+ }
+
+ if (NULL != execute_data->func->op_array.filename) {
+ if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->enable) {
+ terminate_if_writable(ZSTR_VAL(execute_data->func->op_array.filename));
+ }
+}
+
+execute:
+ orig_execute_ex(execute_data);
+}
+
+static int sp_stream_open(const char *filename,
+ zend_file_handle *handle) {
+ const zend_execute_data *data = EG(current_execute_data);
+
+ if ((NULL != data) && (NULL != data->opline) &&
+ (ZEND_INCLUDE_OR_EVAL == data->opline->opcode)) {
+ if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->enable) {
+ terminate_if_writable(filename);
+ }
+ check_inclusion_regexp(filename);
+ }
+ return orig_zend_stream_open(filename, handle);
+}
+
+int hook_execute(void) {
+ TSRMLS_FETCH();
+
+ /* zend_execute_ex is used for "classic" function calls */
+ orig_execute_ex = zend_execute_ex;
+ zend_execute_ex = sp_execute_ex;
+
+ /* zend_stream_open_function is used FIXME */
+ orig_zend_stream_open = zend_stream_open_function;
+ zend_stream_open_function = sp_stream_open;
+
+ /* zend_execute_internal is used for "indirect" functions call,
+ * like array_map or call_user_func. */
+
+ return SUCCESS;
+}
diff --git a/src/sp_execute.h b/src/sp_execute.h
new file mode 100644
index 00000000..83457362
--- /dev/null
+++ b/src/sp_execute.h
@@ -0,0 +1,6 @@
+#ifndef SP_EXECUTE_H
+#define SP_EXECUTE_H
+
+int hook_execute(void);
+
+#endif /* SP_EXECUTE_H */
diff --git a/src/sp_harden_rand.c b/src/sp_harden_rand.c
new file mode 100644
index 00000000..e0e35fff
--- /dev/null
+++ b/src/sp_harden_rand.c
@@ -0,0 +1,77 @@
+#include "php_snuffleupagus.h"
+
+extern ZEND_API zend_class_entry *zend_ce_error;
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
+
+/* This function is needed because `rand` and `mt_rand` parameters
+ * are optional, while the ones from `random_int` aren't. */
+static void random_int_wrapper(INTERNAL_FUNCTION_PARAMETERS) {
+ zend_long min, max, result;
+
+ switch (EX_NUM_ARGS()) {
+ case 0:
+ min = 0;
+ max = PHP_MT_RAND_MAX;
+ break;
+ case 1:
+ ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_QUIET, 1, 1);
+ Z_PARAM_LONG(min);
+ ZEND_PARSE_PARAMETERS_END();
+ max = PHP_MT_RAND_MAX;
+ break;
+ case 2:
+ default:
+ ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_QUIET, 0, 2);
+ Z_PARAM_LONG(min);
+ Z_PARAM_LONG(max);
+ ZEND_PARSE_PARAMETERS_END();
+ }
+
+ if (min > max) {
+ if (php_random_int_throw(max, min, &result) == FAILURE) {
+ return;
+ }
+ } else {
+ if (php_random_int_throw(min, max, &result) == FAILURE) {
+ return;
+ }
+ }
+
+ RETURN_LONG(result);
+}
+
+PHP_FUNCTION(sp_rand) {
+ void (*orig_handler)(INTERNAL_FUNCTION_PARAMETERS);
+
+ if ((orig_handler = zend_hash_str_find_ptr(SNUFFLEUPAGUS_G(sp_internal_functions_hook), "rand",
+ strlen("rand")))) {
+ /* call the original `rand` function,
+ * since we might no be the only ones to hook it*/
+ orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+ }
+
+ random_int_wrapper(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+}
+
+PHP_FUNCTION(sp_mt_rand) {
+ void (*orig_handler)(INTERNAL_FUNCTION_PARAMETERS);
+
+ if ((orig_handler = zend_hash_str_find_ptr(SNUFFLEUPAGUS_G(sp_internal_functions_hook),
+ "mt_rand", strlen("mt_rand")))) {
+ /* call the original `mt_rand` function,
+ * since we might no be the only ones to hook it*/
+ orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+ }
+
+ random_int_wrapper(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+}
+
+int hook_rand() {
+ TSRMLS_FETCH();
+
+ HOOK_FUNCTION("rand", sp_internal_functions_hook, PHP_FN(sp_rand), false);
+ HOOK_FUNCTION("mt_rand", sp_internal_functions_hook, PHP_FN(sp_mt_rand), false);
+
+ return SUCCESS;
+}
diff --git a/src/sp_harden_rand.h b/src/sp_harden_rand.h
new file mode 100644
index 00000000..53ebdd0d
--- /dev/null
+++ b/src/sp_harden_rand.h
@@ -0,0 +1,10 @@
+#ifndef __SP_HARDEN_RAND_H
+#define __SP_HARDEN_RAND_H
+
+#include "ext/standard/php_rand.h"
+#include "ext/standard/php_random.h"
+#include "zend_exceptions.h"
+
+int hook_rand();
+
+#endif /* __SP_HARDEN_RAND_H */
diff --git a/src/sp_list.c b/src/sp_list.c
new file mode 100644
index 00000000..04154b71
--- /dev/null
+++ b/src/sp_list.c
@@ -0,0 +1,37 @@
+#include "sp_list.h"
+#include
+#include
+#include "php_snuffleupagus.h"
+
+void sp_list_free(sp_node_t *node) {
+ while(node) {
+ sp_node_t *tmp = node->next;
+ pefree(node, 1);
+ node = tmp;
+ }
+}
+
+sp_node_t *sp_new_list() {
+ sp_node_t *new = pecalloc(sizeof(*new), 1, 1);
+ new->next = new->data = new->head = NULL;
+ return new;
+}
+
+void sp_list_insert(sp_node_t *list, void *data) {
+ if (list->head == NULL) {
+ list->data = data;
+ list->next = NULL;
+ list->head = list;
+ } else {
+ sp_node_t *new = pecalloc(sizeof(*new), 1, 1);
+
+ new->data = data;
+ new->next = NULL;
+ new->head = list;
+
+ while (list->next) {
+ list = list->next;
+ }
+ list->next = new;
+ }
+}
diff --git a/src/sp_list.h b/src/sp_list.h
new file mode 100644
index 00000000..48b11f68
--- /dev/null
+++ b/src/sp_list.h
@@ -0,0 +1,15 @@
+#ifndef SP_LIST_H
+#define SP_LIST_H
+
+typedef struct sp_node_s {
+ struct sp_node_s *next;
+ struct sp_node_s *head;
+ void *data;
+
+} sp_node_t;
+
+sp_node_t *sp_new_list();
+void sp_list_insert(sp_node_t *, void *);
+void sp_list_free(sp_node_t *);
+
+#endif
diff --git a/src/sp_network_utils.c b/src/sp_network_utils.c
new file mode 100644
index 00000000..28bc3243
--- /dev/null
+++ b/src/sp_network_utils.c
@@ -0,0 +1,159 @@
+#include
+#include
+#include
+
+#include "php_snuffleupagus.h"
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
+
+static inline bool cidr4_match(const struct in_addr addr,
+ const struct in_addr net, uint8_t bits);
+static inline bool cidr6_match(const struct in6_addr address,
+ const struct in6_addr network, uint8_t bits);
+static inline int get_ip_version(const char *ip);
+
+void apply_mask_on_ip(char *out, const char *const remote_addr) {
+ uint8_t mask4 = SNUFFLEUPAGUS_G(config).config_cookie_encryption->mask_ipv4;
+ uint8_t mask6 = SNUFFLEUPAGUS_G(config).config_cookie_encryption->mask_ipv6;
+ const int ip_version = get_ip_version(remote_addr);
+
+ memset(out, 0, 128);
+
+ if (ip_version == AF_INET) {
+ struct in_addr out4;
+ inet_pton(AF_INET, remote_addr, &out4);
+ const long n = out4.s_addr & htonl(0xFFFFFFFFu << (32 - mask4));
+ out[0] = (n >> 24) & 0xFF;
+ out[1] = (n >> 16) & 0xFF;
+ out[2] = (n >> 8) & 0xFF;
+ out[3] = (n >> 0) & 0xFF;
+ } else if (ip_version == AF_INET6) {
+ inet_pton(AF_INET6, remote_addr, out);
+ uint32_t *p_ip = (uint32_t *)out;
+ while (32 < mask6) {
+ *p_ip = 0xFFFFFFFFu;
+ p_ip++;
+ mask6 -= 32;
+ }
+ if (0 != mask6) {
+ *p_ip = htonl(0xFFFFFFFFu << (32 - mask6));
+ }
+ } else {
+ sp_log_err("ip_mask", "It seems that %s isn't a valid ip.", remote_addr);
+ }
+}
+
+/* http://fxr.watson.org/fxr/source/include/net/xfrm.h?v=linux-2.6#L840 */
+static inline bool cidr4_match(const struct in_addr addr,
+ const struct in_addr net, uint8_t bits) {
+ if (bits == 0) { // C99 6.5.7 (3): u32 << 32 is undefined behaviour
+ return true;
+ }
+ return !((addr.s_addr ^ net.s_addr) & htonl(0xFFFFFFFFu << (32 - bits)));
+}
+
+static inline bool cidr6_match(const struct in6_addr address,
+ const struct in6_addr network, uint8_t bits) {
+ //#ifdef LINUX
+ const uint32_t *a = address.s6_addr32;
+ const uint32_t *n = network.s6_addr32;
+ /*
+#else
+ const uint32_t *a = address.__u6_addr.__u6_addr32;
+ const uint32_t *n = network.__u6_addr.__u6_addr32;
+#endif
+*/
+ int bits_whole, bits_incomplete;
+ bits_whole = bits >> 5; // number of whole u32
+ bits_incomplete = bits & 0x1F; // number of bits in incomplete u32
+ if (bits_whole) {
+ if (memcmp(a, n, bits_whole << 2)) {
+ return false;
+ }
+ }
+ if (bits_incomplete) {
+ uint32_t mask = htonl((0xFFFFFFFFu) << (32 - bits_incomplete));
+ if ((a[bits_whole] ^ n[bits_whole]) & mask) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static inline int get_ip_version(const char *ip) {
+ struct in_addr out4;
+ struct in6_addr out6;
+ int res = inet_pton(AF_INET, ip, &out4);
+ if (0 == res) {
+ if (1 == inet_pton(AF_INET6, ip, &out6)) {
+ return AF_INET6;
+ } else {
+ return -1;
+ }
+ } else if (1 == res) {
+ return AF_INET;
+ } else {
+ return -1;
+ }
+}
+
+// TODO factorise a bit this function
+bool cidr_match(const char *ip, const sp_cidr *cidr) {
+ struct in_addr out4;
+ struct in6_addr out6;
+
+ switch (get_ip_version(ip)) {
+ case AF_INET:
+ if (AF_INET != cidr->ip_version) {
+ return false;
+ }
+ inet_pton(AF_INET, ip, &out4);
+ return cidr4_match(out4, cidr->ip.ipv4, cidr->mask);
+ case AF_INET6:
+ if (AF_INET6 != cidr->ip_version) {
+ return false;
+ }
+ inet_pton(AF_INET6, ip, &out6);
+ return cidr6_match(out6, cidr->ip.ipv6, cidr->mask);
+ default:
+ sp_log_err("cidr_match", "Weird ip (%s) family", ip);
+ break;
+ }
+ return false;
+}
+
+int get_ip_and_cidr(char *ip, sp_cidr *cidr) {
+ errno = 0;
+ char *mask = strchr(ip, '/');
+
+ if (NULL == mask) {
+ sp_log_err("config",
+ "'%s' isn't a valid network mask, it seems that you forgot a '/'.",
+ ip);
+ return -1;
+ }
+
+ if (sscanf(mask + 1, "%hhu", &(cidr->mask)) != 1) {
+ sp_log_err("config", "'%s' isn't a valid network mask.", mask + 1);
+ return -1;
+ }
+
+ ip[mask - ip] = '\0'; // NULL the '/' char
+
+ cidr->ip_version = get_ip_version(ip);
+
+ if (AF_INET == cidr->ip_version) {
+ if (cidr->mask > 32) {
+ sp_log_err("config", "'%d' isn't a valid ipv4 mask.", cidr->mask);
+ return -1;
+ }
+ inet_pton(AF_INET, ip, &(cidr->ip.ipv4));
+ } else if (AF_INET6 == cidr->ip_version) {
+ inet_pton(AF_INET6, ip, &(cidr->ip.ipv6));
+ } else {
+ return -1;
+ }
+
+ ip[mask - ip] = '/';
+ return 0;
+}
diff --git a/src/sp_network_utils.h b/src/sp_network_utils.h
new file mode 100644
index 00000000..6b6ce92f
--- /dev/null
+++ b/src/sp_network_utils.h
@@ -0,0 +1,8 @@
+#ifndef SP_NETWORK_UTILS_H
+#define SP_NETWORK_UTILS_H
+
+int get_ip_and_cidr(char *, sp_cidr *);
+bool cidr_match(const char *, const sp_cidr *);
+void apply_mask_on_ip(char *, const char * const);
+
+#endif /*SP_NETWORK_UTILS_H*/
diff --git a/src/sp_unserialize.c b/src/sp_unserialize.c
new file mode 100644
index 00000000..b5b67b42
--- /dev/null
+++ b/src/sp_unserialize.c
@@ -0,0 +1,111 @@
+#include "php_snuffleupagus.h"
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
+
+PHP_FUNCTION(sp_serialize) {
+ void (*orig_handler)(INTERNAL_FUNCTION_PARAMETERS);
+
+ /* Call the original `serialize` function. */
+ if ((orig_handler = zend_hash_str_find_ptr(SNUFFLEUPAGUS_G(sp_internal_functions_hook),
+ "serialize", 9))) {
+ orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+ } else {
+ sp_log_err("disabled_functions",
+ "Unable to find the pointer to the original function 'serialize' in "
+ "the hashtable.\n");
+ }
+
+ /* Compute the HMAC of the textual representation of the serialized data*/
+ zval func_name;
+ zval hmac;
+ zval params[3];
+
+ ZVAL_STRING(&func_name, "hash_hmac");
+ ZVAL_STRING(¶ms[0], "sha256");
+ params[1] = *return_value;
+ ZVAL_STRING(¶ms[2],
+ SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key);
+ call_user_function(CG(function_table), NULL, &func_name, &hmac, 3, params);
+
+ size_t len = Z_STRLEN_P(return_value) + Z_STRLEN(hmac);
+ zend_string *res = zend_string_alloc(len, 0);
+
+ memcpy(ZSTR_VAL(res), Z_STRVAL_P(return_value), Z_STRLEN_P(return_value));
+ memcpy(ZSTR_VAL(res) + Z_STRLEN_P(return_value), Z_STRVAL(hmac),
+ Z_STRLEN(hmac));
+ ZSTR_VAL(res)[len] = '\0';
+
+ /* Append the computed HMAC to the serialized data. */
+ return_value->value.str = res;
+ return;
+}
+
+PHP_FUNCTION(sp_unserialize) {
+ void (*orig_handler)(INTERNAL_FUNCTION_PARAMETERS);
+
+ char *buf = NULL;
+ char *serialized_str = NULL;
+ char *hmac = NULL;
+ zval expected_hmac;
+ size_t buf_len = 0;
+ zval *opts = NULL;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|a", &buf, &buf_len, &opts) ==
+ FAILURE) {
+ RETURN_FALSE;
+ }
+
+ /* 64 is the length of HMAC-256 */
+ if (buf_len < 64) {
+ sp_log_msg("unserialize", LOG_DROP, "The serialized object is too small.");
+ RETURN_FALSE;
+ }
+
+ hmac = buf + buf_len - 64;
+ serialized_str = ecalloc(sizeof(*serialized_str) * (buf_len - 64 + 1), 1);
+ memcpy(serialized_str, buf, buf_len - 64);
+
+ zval func_name;
+ ZVAL_STRING(&func_name, "hash_hmac");
+
+ zval params[3];
+ ZVAL_STRING(¶ms[0], "sha256");
+ ZVAL_STRING(¶ms[1], serialized_str);
+ ZVAL_STRING(¶ms[2],
+ SNUFFLEUPAGUS_G(config).config_snuffleupagus->encryption_key);
+ call_user_function(CG(function_table), NULL, &func_name, &expected_hmac, 3,
+ params);
+
+ unsigned int status = 0;
+ for (uint8_t i = 0; i < 64; i++) {
+ status |= (hmac[i] ^ (Z_STRVAL(expected_hmac))[i]);
+ }
+
+ if (0 == status) {
+ if ((orig_handler = zend_hash_str_find_ptr(SNUFFLEUPAGUS_G(sp_internal_functions_hook),
+ "unserialize", 11))) {
+ orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+ }
+ } else {
+ if ( true == SNUFFLEUPAGUS_G(config).config_unserialize->simulation) {
+ sp_log_msg("unserialize", LOG_NOTICE, "Invalid HMAC for %s", serialized_str);
+ if ((orig_handler = zend_hash_str_find_ptr(SNUFFLEUPAGUS_G(sp_internal_functions_hook),
+ "unserialize", 11))) {
+ orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
+ }
+ } else {
+ sp_log_msg("unserialize", LOG_DROP, "Invalid HMAC for %s", serialized_str);
+ }
+ }
+ efree(serialized_str);
+ return;
+}
+
+int hook_serialize(void) {
+ TSRMLS_FETCH();
+
+ HOOK_FUNCTION("serialize", sp_internal_functions_hook, PHP_FN(sp_serialize), false);
+ HOOK_FUNCTION("unserialize", sp_internal_functions_hook, PHP_FN(sp_unserialize), false);
+
+ return SUCCESS;
+}
diff --git a/src/sp_unserialize.h b/src/sp_unserialize.h
new file mode 100644
index 00000000..557c60c4
--- /dev/null
+++ b/src/sp_unserialize.h
@@ -0,0 +1,9 @@
+#ifndef SP_UNSERIALIZE_H
+#define SP_UNSERIALIZE_H
+
+int hook_serialize(void);
+
+PHP_FUNCTION(sp_serialize);
+PHP_FUNCTION(sp_unserialize);
+
+#endif /* SP_UNSERIALIZE_H */
diff --git a/src/sp_upload_validation.c b/src/sp_upload_validation.c
new file mode 100644
index 00000000..bbd7eae9
--- /dev/null
+++ b/src/sp_upload_validation.c
@@ -0,0 +1,92 @@
+#include "php_snuffleupagus.h"
+#include "rfc1867.h"
+
+ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus);
+
+#define EFREE_3(env) \
+ for (size_t i = 0; i < 4; i++) { \
+ efree(env[i]); \
+ }
+
+void hook_upload() {
+ sp_rfc1867_orig_callback = php_rfc1867_callback;
+ php_rfc1867_callback = sp_rfc1867_callback;
+}
+
+int sp_rfc1867_callback(unsigned int event, void *event_data, void **extra) {
+ int retval = SUCCESS;
+
+ if (sp_rfc1867_orig_callback) {
+ retval = sp_rfc1867_orig_callback(event, event_data, extra);
+ }
+
+ if (event == MULTIPART_EVENT_END) {
+ zend_string *file_key __attribute__((unused)) = NULL;
+ zval *file;
+ pid_t pid;
+
+ sp_log_debug(
+ "Got %d files",
+ zend_hash_num_elements(Z_ARRVAL(PG(http_globals)[TRACK_VARS_FILES])));
+
+ ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL(PG(http_globals)[TRACK_VARS_FILES]),
+ file_key, file) { // for each uploaded file
+
+ char *filename =
+ Z_STRVAL_P(zend_hash_str_find(Z_ARRVAL_P(file), "name", 4));
+ char *tmp_name =
+ Z_STRVAL_P(zend_hash_str_find(Z_ARRVAL_P(file), "tmp_name", 8));
+ size_t filesize =
+ Z_LVAL_P(zend_hash_str_find(Z_ARRVAL_P(file), "size", 4));
+ char *cmd[3] = {0};
+ char *env[5] = {0};
+
+ sp_log_debug("Filename: %s\nTmpname: %s\nSize: %d\nError: %d\nScript: %s",
+ filename, tmp_name, filesize,
+ Z_LVAL_P(zend_hash_str_find(Z_ARRVAL_P(file), "error", 5)),
+ SNUFFLEUPAGUS_G(config).config_upload_validation->script);
+
+ cmd[0] = SNUFFLEUPAGUS_G(config).config_upload_validation->script;
+ cmd[1] = tmp_name;
+ cmd[2] = NULL;
+
+ spprintf(&env[0], 0, "SP_FILENAME=%s", filename);
+ spprintf(&env[1], 0, "SP_REMOTE_ADDR=%s", sp_getenv("REMOTE_ADDR"));
+ spprintf(&env[2], 0, "SP_CURRENT_FILE=%s",
+ zend_get_executed_filename(TSRMLS_C));
+ spprintf(&env[3], 0, "SP_FILESIZE=%zu", filesize);
+ env[4] = NULL;
+
+ if ((pid = fork()) == 0) {
+ if (execve(SNUFFLEUPAGUS_G(config).config_upload_validation->script,
+ cmd, env) == -1) {
+ sp_log_err("upload_validation", "Could not call '%s' : %s",
+ SNUFFLEUPAGUS_G(config).config_upload_validation->script,
+ strerror(errno));
+ EFREE_3(env);
+ exit(1);
+ }
+ } else if (pid == -1) {
+ sp_log_err("upload_validation", "Could not fork process : %s\n",
+ strerror(errno));
+ EFREE_3(env);
+ continue;
+ }
+
+ EFREE_3(env);
+ int waitstatus;
+ wait(&waitstatus);
+ if (WEXITSTATUS(waitstatus) != 0) { // Nope
+ char *uri = sp_getenv("REQUEST_URI");
+ int sim = SNUFFLEUPAGUS_G(config).config_upload_validation->simulation;
+ sp_log_msg("upload_valiation", sim?LOG_NOTICE:LOG_DROP,
+ "The upload of %s on %s was rejected.", filename, uri?uri:"?");
+ if (!SNUFFLEUPAGUS_G(config).config_upload_validation->simulation) {
+ zend_bailout();
+ }
+ }
+ }
+ ZEND_HASH_FOREACH_END();
+ }
+ return retval;
+}
diff --git a/src/sp_upload_validation.h b/src/sp_upload_validation.h
new file mode 100644
index 00000000..3d595275
--- /dev/null
+++ b/src/sp_upload_validation.h
@@ -0,0 +1,9 @@
+#ifndef __SP_UPLOAD_VALIDATION_H__
+#define __SP_UPLOAD_VALIDATION_H__
+
+void hook_upload();
+
+int (*sp_rfc1867_orig_callback)(unsigned int event, void *event_data, void **extra);
+int sp_rfc1867_callback(unsigned int event, void *event_data, void **extra);
+
+#endif
diff --git a/src/sp_utils.c b/src/sp_utils.c
new file mode 100644
index 00000000..087f4314
--- /dev/null
+++ b/src/sp_utils.c
@@ -0,0 +1,425 @@
+#include "php_snuffleupagus.h"
+
+#include
+#include
+#include
+#include
+#include
+
+static inline void _sp_log_err(const char* fmt, ...) {
+ char* msg;
+ va_list args;
+
+ va_start(args, fmt);
+ vspprintf(&msg, 0, fmt, args);
+ va_end(args);
+ php_log_err(msg);
+}
+
+void sp_log_msg(char const *feature, char const *level, const char* fmt, ...) {
+ char* msg;
+ va_list args;
+
+ va_start(args, fmt);
+ vspprintf(&msg, 0, fmt, args);
+ va_end(args);
+
+ char const * const client_ip = sp_getenv("REMOTE_ADDR");
+ _sp_log_err("[snuffleupagus][%s][%s][%s] %s", client_ip?client_ip:"0.0.0.0",
+ feature, level, msg);
+}
+
+int compute_hash(const char* const filename, char* file_hash) {
+ unsigned char buf[1024];
+ unsigned char digest[SHA256_SIZE];
+ PHP_SHA256_CTX context;
+ size_t n;
+
+ php_stream* stream =
+ php_stream_open_wrapper(filename, "rb", REPORT_ERRORS, NULL);
+ if (!stream) {
+ sp_log_err("hash_computation", "Can not open the file %s to compute its hash.\n", filename);
+ return -1;
+ }
+
+ PHP_SHA256Init(&context);
+ while ((n = php_stream_read(stream, (char*)buf, sizeof(buf))) > 0) {
+ PHP_SHA256Update(&context, buf, n);
+ }
+ PHP_SHA256Final(digest, &context);
+ php_stream_close(stream);
+ make_digest_ex(file_hash, digest, SHA256_SIZE);
+ return 0;
+}
+
+static void construct_filename(char* filename, const char* folder) {
+ time_t t = time(NULL);
+ struct tm* tm = localtime(&t); // FIXME use `localtime_r` instead
+ struct timeval tval;
+ struct stat st = {0};
+
+ if (-1 == stat(folder, &st)) {
+ mkdir(folder, 0700);
+ }
+
+ memcpy(filename, folder, strlen(folder));
+ strcat(filename, "sp_dump_");
+ strftime(filename + strlen(filename), 27, "%F_%T:", tm);
+ gettimeofday(&tval, NULL);
+ sprintf(filename + strlen(filename), "%04ld", tval.tv_usec);
+ strcat(filename, "_");
+
+ char* remote_addr = getenv("REMOTE_ADDR");
+ if (remote_addr) {
+ strcat(filename, remote_addr);
+ } else {
+ strcat(filename, "0.0.0.0");
+ }
+ strcat(filename, ".dump");
+}
+
+int sp_log_request(const char* folder) {
+ FILE* file;
+ const char* current_filename = zend_get_executed_filename(TSRMLS_C);
+ const int current_line = zend_get_executed_lineno(TSRMLS_C);
+ char filename[MAX_FOLDER_LEN] = {0};
+ const struct {
+ const char* str;
+ const int key;
+ } zones[] = {{"GET", TRACK_VARS_GET}, {"POST", TRACK_VARS_POST},
+ {"COOKIE", TRACK_VARS_COOKIE}, {"SERVER", TRACK_VARS_SERVER},
+ {"ENV", TRACK_VARS_ENV}, {NULL, 0}};
+
+ construct_filename(filename, folder);
+ if (NULL == (file = fopen(filename, "a"))) {
+ sp_log_err("request_logging", "Unable to open %s", filename);
+ return -1;
+ }
+
+ fprintf(file, "%s:%d\n", current_filename, current_line);
+ for (size_t i = 0; i < (sizeof(zones) / sizeof(zones[0])) - 1; i++) {
+ zval* variable_value;
+ zend_string* variable_key;
+ size_t params_len = strlen(zones[i].str) + 1;
+ char* param;
+ size_t size_max = 2048;
+
+ if (Z_TYPE(PG(http_globals)[zones[i].key]) == IS_UNDEF) {
+ continue;
+ }
+
+ const HashTable* ht = Z_ARRVAL(PG(http_globals)[zones[i].key]);
+
+ // Compute the size of the allocation
+ ZEND_HASH_FOREACH_STR_KEY_VAL(ht, variable_key, variable_value) {
+ params_len += snprintf(NULL, 0, "%s=%s&", ZSTR_VAL(variable_key),
+ Z_STRVAL_P(variable_value));
+ }
+ ZEND_HASH_FOREACH_END();
+
+ params_len = params_len>size_max?size_max:params_len;
+
+#define NCAT_AND_DEC(a, b, c) strncat(a, b, c); c -= strlen(b);
+
+ // Allocate and copy the data
+ // FIXME Why are we even allocating?
+ param = pecalloc(params_len, 1, 0);
+ NCAT_AND_DEC(param, zones[i].str, params_len);
+ NCAT_AND_DEC(param, ":", params_len);
+ ZEND_HASH_FOREACH_STR_KEY_VAL(ht, variable_key, variable_value) {
+ NCAT_AND_DEC(param, ZSTR_VAL(variable_key), params_len);
+ NCAT_AND_DEC(param, "=", params_len);
+ NCAT_AND_DEC(param, Z_STRVAL_P(variable_value), params_len);
+ NCAT_AND_DEC(param, "&", params_len);
+ }
+ ZEND_HASH_FOREACH_END();
+
+ param[strlen(param) - 1] = '\0';
+
+ fputs(param, file);
+ fputs("\n", file);
+ }
+ fclose(file);
+
+#undef CAT_AND_DEC
+ return 0;
+}
+
+static char *zv_str_to_char(zval *zv) {
+ zval copy;
+ char *ret;
+
+
+ ZVAL_ZVAL(©, zv, 1, 0);
+ // str = zend_string_dup(Z_STR_P(zv), 0);
+ for (size_t i = 0; i < Z_STRLEN(copy); i++) {
+ if (Z_STRVAL(copy)[i] == '\0') {
+ Z_STRVAL(copy)[i] = '0';
+ }
+ }
+ ret = estrdup(Z_STRVAL(copy));
+ // zend_string_release(str);
+ return ret;
+}
+
+
+char* sp_convert_to_string(zval* zv) {
+ switch (Z_TYPE_P(zv)) {
+ case IS_FALSE:
+ return estrdup("FALSE");
+ case IS_TRUE:
+ return estrdup("TRUE");
+ case IS_NULL:
+ return estrdup("NULL");
+ case IS_LONG: {
+ char *msg;
+ spprintf(&msg, 0, ZEND_LONG_FMT, Z_LVAL_P(zv));
+ return msg;
+ }
+ case IS_DOUBLE: {
+ char *msg;
+ spprintf(&msg, 0, "%f", Z_DVAL_P(zv));
+ return msg;
+ }
+ case IS_STRING:{
+ return zv_str_to_char(zv);
+ }
+ case IS_OBJECT:
+ return estrdup("OBJECT");
+ case IS_ARRAY:
+ return estrdup("ARRAY");
+ case IS_RESOURCE:
+ return estrdup("RESOURCE");
+ }
+ return estrdup("");
+}
+
+bool sp_match_value(const char* value, const char* to_match, const pcre* rx) {
+ if (to_match) {
+ if (0 == strcmp(value, to_match)) {
+ return true;
+ }
+ } else if (rx) {
+ int substrvec[30];
+ int ret = pcre_exec(rx, NULL, value, strlen(value), 0, 0, substrvec, 30);
+
+ if (ret < 0) {
+ if (ret != PCRE_ERROR_NOMATCH) {
+ sp_log_err("regexp", "Something went wrong with a regexp.");
+ return false;
+ }
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+void sp_log_disable(const char* restrict path, const char* restrict arg_name,
+ const char* restrict arg_value,
+ const sp_disabled_function* config_node) {
+ const char* dump = config_node->dump;
+ const char* alias = config_node->alias;
+ const int sim = config_node->simulation;
+ if (arg_name) {
+ if (alias) {
+ sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
+ "The call to the function '%s' in %s:%d has been disabled, "
+ "because its argument '%s' content (%s) matched the rule '%s'.",
+ path, zend_get_executed_filename(TSRMLS_C),
+ zend_get_executed_lineno(TSRMLS_C), arg_name, arg_value?arg_value:"?",
+ alias);
+ } else {
+ sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
+ "The call to the function '%s' in %s:%d has been disabled, "
+ "because its argument '%s' content (%s) matched a rule.",
+ path, zend_get_executed_filename(TSRMLS_C),
+ zend_get_executed_lineno(TSRMLS_C), arg_name,
+ arg_value?arg_value:"?");
+ }
+ } else {
+ if (alias) {
+ sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
+ "The call to the function '%s' in %s:%d has been disabled, "
+ "because of the the rule '%s'.",path,
+ zend_get_executed_filename(TSRMLS_C),
+ zend_get_executed_lineno(TSRMLS_C), alias);
+ } else {
+ sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
+ "The call to the function '%s' in %s:%d has been disabled.",
+ path, zend_get_executed_filename(TSRMLS_C),
+ zend_get_executed_lineno(TSRMLS_C));
+ }
+ }
+ if (dump) {
+ sp_log_request(config_node->dump);
+ }
+}
+
+void sp_log_disable_ret(const char* restrict path,
+ const char* restrict ret_value,
+ const sp_disabled_function* config_node) {
+ const char* dump = config_node->dump;
+ const char* alias = config_node->alias;
+ const int sim = config_node->simulation;
+ if (alias) {
+ sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
+ "The execution has been aborted in %s:%d, "
+ "because the function '%s' returned '%s', which matched the rule '%s'.",
+ zend_get_executed_filename(TSRMLS_C),
+ zend_get_executed_lineno(TSRMLS_C), path, ret_value?ret_value:"?", alias);
+ } else {
+ sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
+ "The execution has been aborted in %s:%d, "
+ "because the return value (%s) of the function '%s' matched a rule.",
+ zend_get_executed_filename(TSRMLS_C),
+ zend_get_executed_lineno(TSRMLS_C), ret_value?ret_value:"?", path);
+ }
+ if (dump) {
+ sp_log_request(dump);
+ }
+}
+
+int sp_match_array_key(const zval* zv, const char* to_match, const pcre* rx) {
+ zend_string* key;
+ zval* value;
+ char* arg_value_str;
+
+ ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zv), key, value) {
+ if (Z_TYPE_P(value) == IS_ARRAY) {
+ continue;
+ }
+ arg_value_str = sp_convert_to_string(value);
+ if (!sp_match_value(arg_value_str, to_match, rx)) {
+ efree(arg_value_str);
+ continue;
+ } else {
+ efree(arg_value_str);
+ return 1;
+ }
+ }
+ ZEND_HASH_FOREACH_END();
+
+ (void)key; // silence a compiler warning
+
+ return 0;
+}
+
+int sp_match_array_key_recurse(const zval* arr, sp_node_t* keys,
+ const char* to_match, const pcre* rx) {
+ zend_string* key;
+ zval* value;
+ sp_node_t* current = keys;
+ if (current == NULL) {
+ return 0;
+ }
+ ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(arr), key, value) {
+ if (Z_TYPE_P(value) == IS_ARRAY && !strcmp(ZSTR_VAL(key), current->data)) {
+ return sp_match_array_key_recurse(value, current->next, to_match, rx);
+ }
+ if (!strcmp(ZSTR_VAL(key), current->data) && current->next == NULL) {
+ if (!to_match && !rx) {
+ return 1;
+ }
+ if (Z_TYPE_P(value) == IS_ARRAY) {
+ return sp_match_array_key(value, to_match, rx);
+ } else {
+ char *value_str = sp_convert_to_string(value);
+ if (sp_match_value(value_str, to_match, rx)) {
+ efree(value_str);
+ return 1;
+ } else {
+ efree (value_str);
+ return 0;
+ }
+ }
+ }
+ }
+ ZEND_HASH_FOREACH_END();
+ return 0;
+}
+
+zend_always_inline char* sp_getenv(char* var) {
+ if (sapi_module.getenv) {
+ return sapi_module.getenv(ZEND_STRL(var));
+ } else {
+ return getenv(var);
+ }
+}
+
+zend_always_inline int is_regexp_matching(const pcre* regexp, const char* str) {
+ int vec[30];
+ int ret = pcre_exec(regexp, NULL, str, strlen(str), 0, 0, vec, sizeof(vec));
+ if (ret < 0) {
+ if (ret != PCRE_ERROR_NOMATCH) {
+ sp_log_err("regexp", "Something went wrong with a regexp.");
+ }
+ return false;
+ }
+ return true;
+}
+
+int hook_function(const char* original_name, HashTable* hook_table,
+ void (*new_function)(INTERNAL_FUNCTION_PARAMETERS),
+ bool hook_execution_table) {
+ zend_internal_function* func;
+ HashTable *ht = hook_execution_table==true?EG(function_table):CG(function_table);
+
+ /* The `mb` module likes to hook functions, like strlen->mb_strlen,
+ * so we have to hook both of them. */
+ if (0 == strncmp(original_name, "mb_", 3)) {
+ CG(compiler_options) |= ZEND_COMPILE_NO_BUILTIN_STRLEN;
+ if (zend_hash_str_find(ht,
+ VAR_AND_LEN(original_name + 3))) {
+ hook_function(original_name + 3, hook_table, new_function, hook_execution_table);
+ }
+ } else { // TODO this can be moved somewhere else to gain some marginal perfs
+ CG(compiler_options) |= ZEND_COMPILE_NO_BUILTIN_STRLEN;
+ char* mb_name = pecalloc(strlen(original_name) + 3 + 1, 1, 0);
+ memcpy(mb_name, "mb_", 3);
+ memcpy(mb_name + 3, VAR_AND_LEN(original_name));
+ if (zend_hash_str_find(CG(function_table), VAR_AND_LEN(mb_name))) {
+ hook_function(mb_name, hook_table, new_function, hook_execution_table);
+ }
+ }
+
+ if ((func = zend_hash_str_find_ptr(CG(function_table),
+ VAR_AND_LEN(original_name)))) {
+ if (func->handler == new_function) {
+ /* Success !*/
+ } else if (zend_hash_str_add_new_ptr((hook_table),
+ VAR_AND_LEN(original_name),
+ func->handler) == NULL) {
+ sp_log_err("function_pointer_saving",
+ "Could not save function pointer for %s", original_name);
+ return FAILURE;
+ } else {
+ func->handler = new_function;
+ }
+ }
+ return SUCCESS;
+}
+
+int hook_regexp(const pcre* regexp, HashTable* hook_table,
+ void (*new_function)(INTERNAL_FUNCTION_PARAMETERS),
+ bool hook_execution_table) {
+ zend_string* key;
+ HashTable *ht = hook_execution_table==true?EG(function_table):CG(function_table);
+
+ ZEND_HASH_FOREACH_STR_KEY(ht, key) {
+ if (key) {
+ int vec[30];
+ int ret = pcre_exec(regexp, NULL, key->val, key->len, 0, 0, vec, 30);
+ if (ret < 0) { /* Error or no match*/
+ if (PCRE_ERROR_NOMATCH != ret) {
+ sp_log_err("pcre", "Runtime error with pcre, error code: %d", ret);
+ return FAILURE;
+ }
+ continue;
+ }
+ hook_function(key->val, hook_table, new_function, hook_execution_table);
+ }
+ }
+ ZEND_HASH_FOREACH_END();
+ return SUCCESS;
+}
diff --git a/src/sp_utils.h b/src/sp_utils.h
new file mode 100644
index 00000000..37dd2c0f
--- /dev/null
+++ b/src/sp_utils.h
@@ -0,0 +1,68 @@
+#ifndef SP_UTILS_H
+#define SP_UTILS_H
+
+#include "sp_config.h"
+#include "sp_list.h"
+
+#if defined(__GNUC__)
+# if __GNUC__ >= 3
+# define sp_pure __attribute__((pure))
+# define sp_const __attribute__((const))
+# else
+# define sp_pure
+# define sp_const
+# endif
+#endif
+/* The dump filename are of the form
+ * `sp_dump_DATE_IPADDR.dump`, with:
+ * - DATE being the output of asctime, 26 chars long
+ * - IP_ADDR being an IP adress, with a maximum size of 15
+ *
+ * We keep one char for the terminal \0, and one for the leading slash.
+ */
+
+#define MAX_FOLDER_LEN \
+ PATH_MAX - 1 - sizeof("sp_dump_") - 26 - sizeof("_") - 15 - \
+ sizeof(".dump") - 1
+
+#define VAR_AND_LEN(var) var, strlen(var)
+
+#define SHA256_SIZE 32
+
+#define HOOK_FUNCTION(original_name, hook_table, new_function, execution) \
+ hook_function(original_name, SNUFFLEUPAGUS_G(hook_table), new_function, execution)
+
+#define HOOK_FUNCTION_BY_REGEXP(regexp, hook_table, new_function, execution) \
+ hook_regexp(regexp, SNUFFLEUPAGUS_G(hook_table), new_function, execution)
+
+#define LOG_NOTICE "notice"
+#define LOG_DROP "drop"
+#define LOG_DEBUG "debug"
+#define LOG_ERROR "error"
+
+#define sp_log_err(feature, ...) sp_log_msg(feature, LOG_ERROR, __VA_ARGS__)
+#ifdef SP_DEBUG
+ #define sp_log_debug(...) sp_log_msg("DEBUG", LOG_DEBUG, __VA_ARGS__)
+#else
+ #define sp_log_debug(...)
+#endif
+
+void sp_log_msg(char const *feature, char const *level, const char* fmt, ...);
+int compute_hash(const char *const filename, char *file_hash);
+char *sp_convert_to_string(zval *);
+bool sp_match_value(const char *, const char *, const pcre *);
+int sp_match_array_key(const zval *, const char *, const pcre *);
+int sp_match_array_key_recurse(const zval *, sp_node_t *, const char *,
+ const pcre *);
+void sp_log_disable(const char *restrict, const char *restrict,
+ const char *restrict, const sp_disabled_function *);
+void sp_log_disable_ret(const char *restrict, const char *restrict,
+ const sp_disabled_function *);
+char *sp_getenv(char *);
+int is_regexp_matching(const pcre *, const char *);
+int hook_function(const char *, HashTable *,
+ void (*)(INTERNAL_FUNCTION_PARAMETERS), bool);
+int hook_regexp(const pcre *, HashTable *,
+ void (*)(INTERNAL_FUNCTION_PARAMETERS), bool);
+
+#endif /* SP_UTILS_H */
diff --git a/src/tests/broken_conf.phpt b/src/tests/broken_conf.phpt
new file mode 100644
index 00000000..ae0ef6e2
--- /dev/null
+++ b/src/tests/broken_conf.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Invalid configuration prefix for 'this is a broken line'.
+
diff --git a/src/tests/broken_conf2.phpt b/src/tests/broken_conf2.phpt
new file mode 100644
index 00000000..88a22328
--- /dev/null
+++ b/src/tests/broken_conf2.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf2.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Invalid configuration section 'sp.wrong'.
diff --git a/src/tests/broken_conf_config_regexp.phpt b/src/tests/broken_conf_config_regexp.phpt
new file mode 100644
index 00000000..75bc6038
--- /dev/null
+++ b/src/tests/broken_conf_config_regexp.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_config_regexp.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Failed to compile '*.': nothing to repeat.
+[snuffleupagus][0.0.0.0][config][error] '.filename_r()' is expecting a valid regexp, and not '"*."'.
diff --git a/src/tests/broken_conf_enable_disable.phpt b/src/tests/broken_conf_enable_disable.phpt
new file mode 100644
index 00000000..2f3fe195
--- /dev/null
+++ b/src/tests/broken_conf_enable_disable.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Global strict mode
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/borken_conf_enable_disable.ini
+--FILE--
+--EXPECTF--
+[snuffleupagus][0.0.0.0][config][error] A rule can't be enabled and disabled.
diff --git a/src/tests/broken_conf_expecting_bool.phpt b/src/tests/broken_conf_expecting_bool.phpt
new file mode 100644
index 00000000..80e1b61d
--- /dev/null
+++ b/src/tests/broken_conf_expecting_bool.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Bad boolean value in configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_expecting_bool.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Trailing chars '337);' at the end of '.enable(1337);'.
diff --git a/src/tests/broken_conf_expecting_int.phpt b/src/tests/broken_conf_expecting_int.phpt
new file mode 100644
index 00000000..e8063370
--- /dev/null
+++ b/src/tests/broken_conf_expecting_int.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Bad integer value in configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_expecting_int.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][error][error] .mask_ipv4() is expecting a valid integer.
diff --git a/src/tests/broken_conf_invalid_cidr.phpt b/src/tests/broken_conf_invalid_cidr.phpt
new file mode 100644
index 00000000..515091b0
--- /dev/null
+++ b/src/tests/broken_conf_invalid_cidr.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_invalid_cidr.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] '42' isn't a valid ipv4 mask.
diff --git a/src/tests/broken_conf_invalid_cidr6.phpt b/src/tests/broken_conf_invalid_cidr6.phpt
new file mode 100644
index 00000000..d20cfcd0
--- /dev/null
+++ b/src/tests/broken_conf_invalid_cidr6.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_invalid_cidr6.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] 'ZZZ' isn't a valid network mask.
diff --git a/src/tests/broken_conf_invalid_cidr6_no_slash.phpt b/src/tests/broken_conf_invalid_cidr6_no_slash.phpt
new file mode 100644
index 00000000..de70a05b
--- /dev/null
+++ b/src/tests/broken_conf_invalid_cidr6_no_slash.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration, invalid cidr for ipv6 because there is no `/` in it
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_invalid_cidr6_no_slash.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] '2001:0db8:0000:0000:0000:ff00:0042:8329' isn't a valid network mask, it seems that you forgot a '/'.
diff --git a/src/tests/broken_conf_invalid_cidr6_too_big.phpt b/src/tests/broken_conf_invalid_cidr6_too_big.phpt
new file mode 100644
index 00000000..47d4a5de
--- /dev/null
+++ b/src/tests/broken_conf_invalid_cidr6_too_big.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration, cidr for ipv6 is too big, that will `mod` to 25.
+(13337%128 = 25)
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_invalid_cidr6_too_big.ini
+--FILE--
+--EXPECT--
diff --git a/src/tests/broken_conf_invalid_cidr_value.phpt b/src/tests/broken_conf_invalid_cidr_value.phpt
new file mode 100644
index 00000000..712f123a
--- /dev/null
+++ b/src/tests/broken_conf_invalid_cidr_value.phpt
@@ -0,0 +1,11 @@
+--TEST--
+Broken configuration, invalid cidr value
+(13337%128 = 25)
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_invalid_cidr_value.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][error][error] There is an issue with the parsing of '"': it doesn't look like a valid string.
+[snuffleupagus][0.0.0.0][config][error] " doesn't contain a valid cidr.
diff --git a/src/tests/broken_conf_invalid_type.phpt b/src/tests/broken_conf_invalid_type.phpt
new file mode 100644
index 00000000..29d2ff57
--- /dev/null
+++ b/src/tests/broken_conf_invalid_type.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken conf with wrong type
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_invalid_type.ini
+--FILE--
+--EXPECTF--
+[snuffleupagus][0.0.0.0][error][error] There is an issue with the parsing of '"totally_wrong"_type")': it doesn't look like a valid string.
diff --git a/src/tests/broken_conf_line_empty_string.phpt b/src/tests/broken_conf_line_empty_string.phpt
new file mode 100644
index 00000000..c4334b97
--- /dev/null
+++ b/src/tests/broken_conf_line_empty_string.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Configuration line with an empty string
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_line_empty_string.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][error][error] There is an issue with the parsing of '': it doesn't look like a valid string.
diff --git a/src/tests/broken_conf_line_no_closing.phpt b/src/tests/broken_conf_line_no_closing.phpt
new file mode 100644
index 00000000..07c94e41
--- /dev/null
+++ b/src/tests/broken_conf_line_no_closing.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Configuration line without closing parenthese
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_line_no_closing.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][error][error] There is an issue with the parsing of '"123"': it doesn't look like a valid string.
diff --git a/src/tests/broken_conf_line_too_long.phpt b/src/tests/broken_conf_line_too_long.phpt
new file mode 100644
index 00000000..8e82708c
--- /dev/null
+++ b/src/tests/broken_conf_line_too_long.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Line too long in configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_line_too_long.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] The following line is too long: 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111);.
+[snuffleupagus][0.0.0.0][error][error] .mask_ipv4() is expecting a valid integer.
diff --git a/src/tests/broken_conf_lots_of_quotes.phpt b/src/tests/broken_conf_lots_of_quotes.phpt
new file mode 100644
index 00000000..e877cfa4
--- /dev/null
+++ b/src/tests/broken_conf_lots_of_quotes.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Configuration line with too many quotes
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_lots_of_quotes.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][error][error] There is an issue with the parsing of '"this\"is a weird\"\"\"cookie\"name"");': it doesn't look like a valid string.
diff --git a/src/tests/broken_conf_mutually_exclusive.phpt b/src/tests/broken_conf_mutually_exclusive.phpt
new file mode 100644
index 00000000..9de7e5a5
--- /dev/null
+++ b/src/tests/broken_conf_mutually_exclusive.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_mutually_exclusive.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Invalid configuration line: 'sp.disabled_functions.function("system").param("id").value("42").value_r("^id$").drop();':'.value' and '.regexp' are mutually exclusives.
\ No newline at end of file
diff --git a/src/tests/broken_conf_mutually_exclusive2.phpt b/src/tests/broken_conf_mutually_exclusive2.phpt
new file mode 100644
index 00000000..9d3ea361
--- /dev/null
+++ b/src/tests/broken_conf_mutually_exclusive2.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_mutually_exclusive2.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Invalid configuration line: 'sp.disabled_functions.function("system").function_r("system").param("id").value("42").drop();': '.r_function' and '.function' are mutually exclusive.
\ No newline at end of file
diff --git a/src/tests/broken_conf_mutually_exclusive3.phpt b/src/tests/broken_conf_mutually_exclusive3.phpt
new file mode 100644
index 00000000..58686a31
--- /dev/null
+++ b/src/tests/broken_conf_mutually_exclusive3.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_mutually_exclusive3.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Invalid configuration line: 'sp.disabled_functions.function("system").param("id").value("42").filename_r("^id$").filename("pouet.txt").drop();':'.r_filename' and '.filename' are mutually exclusive.
\ No newline at end of file
diff --git a/src/tests/broken_conf_mutually_exclusive4.phpt b/src/tests/broken_conf_mutually_exclusive4.phpt
new file mode 100644
index 00000000..d854380d
--- /dev/null
+++ b/src/tests/broken_conf_mutually_exclusive4.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_mutually_exclusive4.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Invalid configuration line: 'sp.disabled_functions.function("system").param("id").value("42").param_r("^id$").drop();':'.r_param' and '.param' are mutually exclusive.
\ No newline at end of file
diff --git a/src/tests/broken_conf_mutually_exclusive5.phpt b/src/tests/broken_conf_mutually_exclusive5.phpt
new file mode 100644
index 00000000..a265c30e
--- /dev/null
+++ b/src/tests/broken_conf_mutually_exclusive5.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_mutually_exclusive5.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Invalid configuration line: 'sp.disabled_functions.function("system").ret("0").drop().ret_r("^0$");':'.r_ret' and '.ret' are mutually exclusive.
\ No newline at end of file
diff --git a/src/tests/broken_conf_mutually_exclusive6.phpt b/src/tests/broken_conf_mutually_exclusive6.phpt
new file mode 100644
index 00000000..d0cdb855
--- /dev/null
+++ b/src/tests/broken_conf_mutually_exclusive6.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_mutually_exclusive6.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Invalid configuration line: 'sp.disabled_functions.function("system").param("id").value("42").ret_r("^0$").drop();':`ret` and `param` are mutually exclusives.
\ No newline at end of file
diff --git a/src/tests/broken_conf_mutually_exclusive7.phpt b/src/tests/broken_conf_mutually_exclusive7.phpt
new file mode 100644
index 00000000..c9a35134
--- /dev/null
+++ b/src/tests/broken_conf_mutually_exclusive7.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_mutually_exclusive7.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Invalid configuration line: 'sp.disabled_functions.function("system").ret("0").drop().allow();': The rule must either be a `drop` or and `allow` one.
\ No newline at end of file
diff --git a/src/tests/broken_conf_mutually_exclusive8.phpt b/src/tests/broken_conf_mutually_exclusive8.phpt
new file mode 100644
index 00000000..7c5baeeb
--- /dev/null
+++ b/src/tests/broken_conf_mutually_exclusive8.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_mutually_exclusive8.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Invalid configuration line: 'sp.disabled_functions.ret("0").drop();': must take a function name.
\ No newline at end of file
diff --git a/src/tests/broken_conf_no_closing_misc.phpt b/src/tests/broken_conf_no_closing_misc.phpt
new file mode 100644
index 00000000..1d1e112b
--- /dev/null
+++ b/src/tests/broken_conf_no_closing_misc.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Configuration line without closing parenthese, misc
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_no_closing_misc.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Missing closing ) in line 123.
+[snuffleupagus][0.0.0.0][error][error] .mask_ipv4() is expecting a valid integer.
diff --git a/src/tests/broken_conf_weird_keyword.phpt b/src/tests/broken_conf_weird_keyword.phpt
new file mode 100644
index 00000000..52937913
--- /dev/null
+++ b/src/tests/broken_conf_weird_keyword.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Bad config, unknown keyword
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_weird_keyword.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][config][error] Trailing chars '.not_a_valid_keyword("test");' at the end of '.enable().not_a_valid_keyword("test");'.
\ No newline at end of file
diff --git a/src/tests/broken_conf_wrong_quotes.phpt b/src/tests/broken_conf_wrong_quotes.phpt
new file mode 100644
index 00000000..b6324fea
--- /dev/null
+++ b/src/tests/broken_conf_wrong_quotes.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Configuration line with too many quotes
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_wrong_quotes.ini
+--FILE--
+--EXPECT--
+[snuffleupagus][0.0.0.0][error][error] There is an issue with the parsing of '"\)': it doesn't look like a valid string.
diff --git a/src/tests/broken_conf_wrong_type.phpt b/src/tests/broken_conf_wrong_type.phpt
new file mode 100644
index 00000000..338ca3a7
--- /dev/null
+++ b/src/tests/broken_conf_wrong_type.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken conf with wrong type
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_conf_wrong_type.ini
+--FILE--
+--EXPECTF--
+[snuffleupagus][0.0.0.0][error][error] .ret_type() is expecting a valid php type ('false', 'true', 'array'. 'object', 'long', 'double', 'null', 'resource', 'reference', 'undef').
diff --git a/src/tests/broken_regexp.phpt b/src/tests/broken_regexp.phpt
new file mode 100644
index 00000000..cbfef7df
--- /dev/null
+++ b/src/tests/broken_regexp.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Broken regexp
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/broken_regexp.ini
+--FILE--
+--EXPECTF--
+[snuffleupagus][0.0.0.0][config][error] '.value_r()' is expecting a valid regexp, and not '"^$["'.
diff --git a/src/tests/config/borken_conf_enable_disable.ini b/src/tests/config/borken_conf_enable_disable.ini
new file mode 100644
index 00000000..4e952940
--- /dev/null
+++ b/src/tests/config/borken_conf_enable_disable.ini
@@ -0,0 +1 @@
+sp.global_strict.disable().enable();
diff --git a/src/tests/config/broken_conf.ini b/src/tests/config/broken_conf.ini
new file mode 100644
index 00000000..0595320e
--- /dev/null
+++ b/src/tests/config/broken_conf.ini
@@ -0,0 +1 @@
+this is a broken line
diff --git a/src/tests/config/broken_conf2.ini b/src/tests/config/broken_conf2.ini
new file mode 100644
index 00000000..fdb6b8f0
--- /dev/null
+++ b/src/tests/config/broken_conf2.ini
@@ -0,0 +1 @@
+sp.wrong
diff --git a/src/tests/config/broken_conf_expecting_bool.ini b/src/tests/config/broken_conf_expecting_bool.ini
new file mode 100644
index 00000000..51c28b2f
--- /dev/null
+++ b/src/tests/config/broken_conf_expecting_bool.ini
@@ -0,0 +1,5 @@
+ # this is an example of broken conf
+
+
+ ; this is another comment
+sp.harden_random.enable(1337);
diff --git a/src/tests/config/broken_conf_expecting_int.ini b/src/tests/config/broken_conf_expecting_int.ini
new file mode 100644
index 00000000..8e2efea7
--- /dev/null
+++ b/src/tests/config/broken_conf_expecting_int.ini
@@ -0,0 +1,2 @@
+sp.global.secret_key("abcdef");
+sp.cookie_encryption.cookie("super_cookie").mask_ipv4(abc);
diff --git a/src/tests/config/broken_conf_invalid_cidr.ini b/src/tests/config/broken_conf_invalid_cidr.ini
new file mode 100644
index 00000000..0cdc695a
--- /dev/null
+++ b/src/tests/config/broken_conf_invalid_cidr.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").drop().cidr("127.0.0.1/42");
diff --git a/src/tests/config/broken_conf_invalid_cidr6.ini b/src/tests/config/broken_conf_invalid_cidr6.ini
new file mode 100644
index 00000000..e5a120c7
--- /dev/null
+++ b/src/tests/config/broken_conf_invalid_cidr6.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").drop().cidr("2001:0db8:0000:0000:0000:ff00:0042:8329/ZZZ");
diff --git a/src/tests/config/broken_conf_invalid_cidr6_no_slash.ini b/src/tests/config/broken_conf_invalid_cidr6_no_slash.ini
new file mode 100644
index 00000000..e4cf8355
--- /dev/null
+++ b/src/tests/config/broken_conf_invalid_cidr6_no_slash.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").drop().cidr("2001:0db8:0000:0000:0000:ff00:0042:8329");
diff --git a/src/tests/config/broken_conf_invalid_cidr6_too_big.ini b/src/tests/config/broken_conf_invalid_cidr6_too_big.ini
new file mode 100644
index 00000000..417dee71
--- /dev/null
+++ b/src/tests/config/broken_conf_invalid_cidr6_too_big.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").drop().cidr("2001:0db8:0000:0000:0000:ff00:0042:8329/13337");
diff --git a/src/tests/config/broken_conf_invalid_cidr_value.ini b/src/tests/config/broken_conf_invalid_cidr_value.ini
new file mode 100644
index 00000000..733e8891
--- /dev/null
+++ b/src/tests/config/broken_conf_invalid_cidr_value.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").drop().cidr("
diff --git a/src/tests/config/broken_conf_invalid_type.ini b/src/tests/config/broken_conf_invalid_type.ini
new file mode 100644
index 00000000..b2cd8cd3
--- /dev/null
+++ b/src/tests/config/broken_conf_invalid_type.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("strpos").ret_type("totally_wrong"_type")
diff --git a/src/tests/config/broken_conf_line_empty_string.ini b/src/tests/config/broken_conf_line_empty_string.ini
new file mode 100644
index 00000000..74d0e5a2
--- /dev/null
+++ b/src/tests/config/broken_conf_line_empty_string.ini
@@ -0,0 +1 @@
+sp.cookie_encryption.mask_ipv4(123).cookie(
diff --git a/src/tests/config/broken_conf_line_no_closing.ini b/src/tests/config/broken_conf_line_no_closing.ini
new file mode 100644
index 00000000..bcac291c
--- /dev/null
+++ b/src/tests/config/broken_conf_line_no_closing.ini
@@ -0,0 +1 @@
+sp.cookie_encryption.mask_ipv4(123).cookie("123"
diff --git a/src/tests/config/broken_conf_line_too_long.ini b/src/tests/config/broken_conf_line_too_long.ini
new file mode 100644
index 00000000..ed057a50
--- /dev/null
+++ b/src/tests/config/broken_conf_line_too_long.ini
@@ -0,0 +1 @@
+sp.cookie_encryption.cookie("super_cookie").mask_ipv4(1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111);
diff --git a/src/tests/config/broken_conf_lots_of_quotes.ini b/src/tests/config/broken_conf_lots_of_quotes.ini
new file mode 100644
index 00000000..dfd48e74
--- /dev/null
+++ b/src/tests/config/broken_conf_lots_of_quotes.ini
@@ -0,0 +1 @@
+sp.cookie_encryption.mask_ipv4(123).cookie("this\"is a weird\"\"\"cookie\"name"");
diff --git a/src/tests/config/broken_conf_mutually_exclusive.ini b/src/tests/config/broken_conf_mutually_exclusive.ini
new file mode 100644
index 00000000..af1d5058
--- /dev/null
+++ b/src/tests/config/broken_conf_mutually_exclusive.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").param("id").value("42").value_r("^id$").drop();
diff --git a/src/tests/config/broken_conf_mutually_exclusive2.ini b/src/tests/config/broken_conf_mutually_exclusive2.ini
new file mode 100644
index 00000000..29b21d47
--- /dev/null
+++ b/src/tests/config/broken_conf_mutually_exclusive2.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").function_r("system").param("id").value("42").drop();
diff --git a/src/tests/config/broken_conf_mutually_exclusive3.ini b/src/tests/config/broken_conf_mutually_exclusive3.ini
new file mode 100644
index 00000000..556de08e
--- /dev/null
+++ b/src/tests/config/broken_conf_mutually_exclusive3.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").param("id").value("42").filename_r("^id$").filename("pouet.txt").drop();
diff --git a/src/tests/config/broken_conf_mutually_exclusive4.ini b/src/tests/config/broken_conf_mutually_exclusive4.ini
new file mode 100644
index 00000000..d212ad43
--- /dev/null
+++ b/src/tests/config/broken_conf_mutually_exclusive4.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").param("id").value("42").param_r("^id$").drop();
diff --git a/src/tests/config/broken_conf_mutually_exclusive5.ini b/src/tests/config/broken_conf_mutually_exclusive5.ini
new file mode 100644
index 00000000..5b64079b
--- /dev/null
+++ b/src/tests/config/broken_conf_mutually_exclusive5.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").ret("0").drop().ret_r("^0$");
diff --git a/src/tests/config/broken_conf_mutually_exclusive6.ini b/src/tests/config/broken_conf_mutually_exclusive6.ini
new file mode 100644
index 00000000..d08ee58b
--- /dev/null
+++ b/src/tests/config/broken_conf_mutually_exclusive6.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").param("id").value("42").ret_r("^0$").drop();
diff --git a/src/tests/config/broken_conf_mutually_exclusive7.ini b/src/tests/config/broken_conf_mutually_exclusive7.ini
new file mode 100644
index 00000000..645c26ce
--- /dev/null
+++ b/src/tests/config/broken_conf_mutually_exclusive7.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").ret("0").drop().allow();
diff --git a/src/tests/config/broken_conf_mutually_exclusive8.ini b/src/tests/config/broken_conf_mutually_exclusive8.ini
new file mode 100644
index 00000000..b08ef578
--- /dev/null
+++ b/src/tests/config/broken_conf_mutually_exclusive8.ini
@@ -0,0 +1 @@
+sp.disable_functions.ret("0").drop();
diff --git a/src/tests/config/broken_conf_no_closing_misc.ini b/src/tests/config/broken_conf_no_closing_misc.ini
new file mode 100644
index 00000000..2cb79a82
--- /dev/null
+++ b/src/tests/config/broken_conf_no_closing_misc.ini
@@ -0,0 +1 @@
+sp.cookie_encryption.cookie("123").mask_ipv4(123
diff --git a/src/tests/config/broken_conf_to_few_args.ini b/src/tests/config/broken_conf_to_few_args.ini
new file mode 100644
index 00000000..89e19be4
--- /dev/null
+++ b/src/tests/config/broken_conf_to_few_args.ini
@@ -0,0 +1 @@
+sp.harden_random.enable();
diff --git a/src/tests/config/broken_conf_weird_keyword.ini b/src/tests/config/broken_conf_weird_keyword.ini
new file mode 100644
index 00000000..bf5e7f5c
--- /dev/null
+++ b/src/tests/config/broken_conf_weird_keyword.ini
@@ -0,0 +1 @@
+sp.harden_random.enable().not_a_valid_keyword("test");
diff --git a/src/tests/config/broken_conf_wrong_quotes.ini b/src/tests/config/broken_conf_wrong_quotes.ini
new file mode 100644
index 00000000..c8cc9493
--- /dev/null
+++ b/src/tests/config/broken_conf_wrong_quotes.ini
@@ -0,0 +1 @@
+sp.cookie_encryption.mask_ipv4(123).cookie("\)
diff --git a/src/tests/config/broken_conf_wrong_type.ini b/src/tests/config/broken_conf_wrong_type.ini
new file mode 100644
index 00000000..6ecca6a6
--- /dev/null
+++ b/src/tests/config/broken_conf_wrong_type.ini
@@ -0,0 +1,5 @@
+sp.disable_functions.function("strpos").ret_type("undef").drop().alias("Return value is undef");
+sp.disable_functions.function("strpos").ret_type("null").drop().alias("Return value is null");
+sp.disable_functions.function("strpos").ret_type("object").drop().alias("Return value is object");
+sp.disable_functions.function("strpos").ret_type("reference").drop().alias("Return value is reference");
+sp.disable_functions.function("strpos").ret_type("totally_wrong_type").drop().alias("Return value is FALSE");
diff --git a/src/tests/config/broken_config_regexp.ini b/src/tests/config/broken_config_regexp.ini
new file mode 100644
index 00000000..efad83ef
--- /dev/null
+++ b/src/tests/config/broken_config_regexp.ini
@@ -0,0 +1 @@
+sp.disable_functions.function_r("^system$").filename_r("*.").drop();
diff --git a/src/tests/config/broken_regexp.ini b/src/tests/config/broken_regexp.ini
new file mode 100644
index 00000000..8e1f69a9
--- /dev/null
+++ b/src/tests/config/broken_regexp.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("AwesomeClass::method3").param("a").drop().value_r("^$[");
diff --git a/src/tests/config/config_disable_writable.ini b/src/tests/config/config_disable_writable.ini
new file mode 100644
index 00000000..9f906019
--- /dev/null
+++ b/src/tests/config/config_disable_writable.ini
@@ -0,0 +1 @@
+ sp.readonly_exec.enable();
diff --git a/src/tests/config/config_disable_writable_disabled.ini b/src/tests/config/config_disable_writable_disabled.ini
new file mode 100644
index 00000000..6a33437b
--- /dev/null
+++ b/src/tests/config/config_disable_writable_disabled.ini
@@ -0,0 +1 @@
+ sp.readonly_exec.disable();
diff --git a/src/tests/config/config_disable_writable_simulation.ini b/src/tests/config/config_disable_writable_simulation.ini
new file mode 100644
index 00000000..52a43ba4
--- /dev/null
+++ b/src/tests/config/config_disable_writable_simulation.ini
@@ -0,0 +1 @@
+ sp.readonly_exec.enable().simulation();
diff --git a/src/tests/config/config_disabled_functions_filename_r.ini b/src/tests/config/config_disabled_functions_filename_r.ini
new file mode 100644
index 00000000..b92f1360
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_filename_r.ini
@@ -0,0 +1,2 @@
+sp.disable_functions.function_r("^system$").filename_r("\\.txt$").drop();
+sp.disable_functions.function_r("^shell_exec$").filename_r("\\.php$").drop();
diff --git a/src/tests/config/config_disabled_functions_method.ini b/src/tests/config/config_disabled_functions_method.ini
new file mode 100644
index 00000000..4d088d20
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_method.ini
@@ -0,0 +1,3 @@
+sp.disable_functions.function("AwesomeClass::method1").drop();
+sp.disable_functions.function("method2").drop();
+sp.disable_functions.function("AwesomeClass::method3").param("a").value("pouet").drop();
diff --git a/src/tests/config/config_disabled_functions_name_r.ini b/src/tests/config/config_disabled_functions_name_r.ini
new file mode 100644
index 00000000..3f7178e0
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_name_r.ini
@@ -0,0 +1,2 @@
+sp.disable_functions.function_r("^not_system$").ret("42").drop();
+sp.disable_functions.function_r("^system$").ret("1337").drop();
diff --git a/src/tests/config/config_disabled_functions_name_type.ini b/src/tests/config/config_disabled_functions_name_type.ini
new file mode 100644
index 00000000..2b433dfe
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_name_type.ini
@@ -0,0 +1 @@
+sp.disable_functions.function_r("^strcmp$").param("str1").param_type("array").drop();
diff --git a/src/tests/config/config_disabled_functions_namespace.ini b/src/tests/config/config_disabled_functions_namespace.ini
new file mode 100644
index 00000000..d09b81bb
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_namespace.ini
@@ -0,0 +1,2 @@
+sp.disable_functions.function("strcmp").drop();
+sp.disable_functions.function("my_super_namespace::my_function").drop();
diff --git a/src/tests/config/config_disabled_functions_nul_byte.ini b/src/tests/config/config_disabled_functions_nul_byte.ini
new file mode 100644
index 00000000..79945835
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_nul_byte.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").param("command").value_r("id").drop();
\ No newline at end of file
diff --git a/src/tests/config/config_disabled_functions_param.ini b/src/tests/config/config_disabled_functions_param.ini
new file mode 100644
index 00000000..7363781e
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_param.ini
@@ -0,0 +1,6 @@
+sp.disable_functions.function("system").param("command").value_r("^id$").alias("1").drop();
+sp.disable_functions.function("array_sum").param("array").value_r("^8$").alias("2").drop();
+sp.disable_functions.function("shell_exec").param("cmd").value("id").alias("3").drop();
+sp.disable_functions.function("shell_exec").param("cmd").value("bla").alias("4").drop();
+sp.disable_functions.function("strcmp").param("str1").value("bla").alias("5").drop().simulation();
+sp.disable_functions.function("strncmp").param("str1").value("bla").drop().simulation();
diff --git a/src/tests/config/config_disabled_functions_param_alias.ini b/src/tests/config/config_disabled_functions_param_alias.ini
new file mode 100644
index 00000000..f8d9f439
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_param_alias.ini
@@ -0,0 +1,2 @@
+sp.disable_functions.function("system").alias("1").drop();
+sp.disable_functions.function("shell_exec").alias("2").drop().simulation();
diff --git a/src/tests/config/config_disabled_functions_param_allow.ini b/src/tests/config/config_disabled_functions_param_allow.ini
new file mode 100644
index 00000000..e349b386
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_param_allow.ini
@@ -0,0 +1,3 @@
+sp.disable_functions.function("system").param("command").value("echo win").filename("test.php").drop();
+sp.disable_functions.function("system").param("command").value("echo win").allow();
+sp.disable_functions.function("system").drop();
diff --git a/src/tests/config/config_disabled_functions_param_array.ini b/src/tests/config/config_disabled_functions_param_array.ini
new file mode 100644
index 00000000..7b716925
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_param_array.ini
@@ -0,0 +1,4 @@
+sp.disable_functions.function("foo").param("arr").value("abcd").alias("1").drop();
+sp.disable_functions.function("foo").param("arr[bla]").value("abcdef").alias("2").drop();
+sp.disable_functions.function("foo").param("arr[test]").alias("3").drop();
+sp.disable_functions.function("foo").param("arr[test2][foo][lol]").value("aaa").alias("4").drop();
diff --git a/src/tests/config/config_disabled_functions_param_int.ini b/src/tests/config/config_disabled_functions_param_int.ini
new file mode 100644
index 00000000..2552f0a2
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_param_int.ini
@@ -0,0 +1,2 @@
+sp.disable_functions.function("foobar").param("id").value("42").drop();
+sp.disable_functions.function("foobar").param("id").value_r("^1337").drop();
diff --git a/src/tests/config/config_disabled_functions_param_r.ini b/src/tests/config/config_disabled_functions_param_r.ini
new file mode 100644
index 00000000..d9f6692e
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_param_r.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").param_r("^command$").value("id").drop();
diff --git a/src/tests/config/config_disabled_functions_param_runtime.ini b/src/tests/config/config_disabled_functions_param_runtime.ini
new file mode 100644
index 00000000..641bd0ad
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_param_runtime.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("test").param("param").value_r("1337").drop();
diff --git a/src/tests/config/config_disabled_functions_param_str_representation.ini b/src/tests/config/config_disabled_functions_param_str_representation.ini
new file mode 100644
index 00000000..7171a301
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_param_str_representation.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("var_export").param("var").value("bla").drop();
diff --git a/src/tests/config/config_disabled_functions_require.ini b/src/tests/config/config_disabled_functions_require.ini
new file mode 100644
index 00000000..474fadac
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_require.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("require").param("").value_r("meh$").drop();
diff --git a/src/tests/config/config_disabled_functions_ret_allow.ini b/src/tests/config/config_disabled_functions_ret_allow.ini
new file mode 100644
index 00000000..18842271
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_ret_allow.ini
@@ -0,0 +1,2 @@
+sp.disable_functions.function("strpos").hash("70b33f3eaf585b245640bb2c92445d0040b2bcb31395aa25dede9f2df4dbcbe8").allow();
+sp.disable_functions.function("strpos").drop();
diff --git a/src/tests/config/config_disabled_functions_ret_allow_value.ini b/src/tests/config/config_disabled_functions_ret_allow_value.ini
new file mode 100644
index 00000000..e179819b
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_ret_allow_value.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("strpos").ret("0").allow();
diff --git a/src/tests/config/config_disabled_functions_ret_right_hash.ini b/src/tests/config/config_disabled_functions_ret_right_hash.ini
new file mode 100644
index 00000000..6f49177b
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_ret_right_hash.ini
@@ -0,0 +1,4 @@
+sp.disable_functions.function("system").ret("1").drop();
+sp.disable_functions.function("system").ret("1337").hash("123456789597a81a2b862cdb49920e2cba2e5979a3fc374c58c803e8f5c99a10").drop();
+sp.disable_functions.function("system").ret("1338").hash("522a976fa597a81a2b862cdb49920e2cba2e5979a3fc374c58c803e8f5c99a10").drop();
+sp.disable_functions.function("system").ret("1337").hash("522a976fa597a81a2b862cdb49920e2cba2e5979a3fc374c58c803e8f5c99a10").drop();
diff --git a/src/tests/config/config_disabled_functions_ret_simulation.ini b/src/tests/config/config_disabled_functions_ret_simulation.ini
new file mode 100644
index 00000000..ee46c4be
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_ret_simulation.ini
@@ -0,0 +1,3 @@
+sp.disable_functions.function("strpos").ret("0").simulation().drop();
+sp.disable_functions.function("stripos").ret("0").simulation().drop().alias("1");
+sp.disable_functions.function("strcmp").ret("0").drop();
diff --git a/src/tests/config/config_disabled_functions_right_hash.ini b/src/tests/config/config_disabled_functions_right_hash.ini
new file mode 100644
index 00000000..fab68fa6
--- /dev/null
+++ b/src/tests/config/config_disabled_functions_right_hash.ini
@@ -0,0 +1,3 @@
+sp.disable_functions.function("system").hash("1337c3ad8cf096272cd0e78768af3b11325f498de5c2c36f40adc43643af378a").allow();
+sp.disable_functions.function("system").hash("d259c3ad8cf096272cd0e78768af3b11325f498de5c2c36f40adc43643af378a").allow();
+sp.disable_functions.function("system").drop();
\ No newline at end of file
diff --git a/src/tests/config/config_disabled_user_functions.ini b/src/tests/config/config_disabled_user_functions.ini
new file mode 100644
index 00000000..15cbccc8
--- /dev/null
+++ b/src/tests/config/config_disabled_user_functions.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("my_super_function").drop();
diff --git a/src/tests/config/config_encrypted_cookies.ini b/src/tests/config/config_encrypted_cookies.ini
new file mode 100644
index 00000000..710e8632
--- /dev/null
+++ b/src/tests/config/config_encrypted_cookies.ini
@@ -0,0 +1,3 @@
+sp.global.secret_key("abcdef");
+sp.cookie_encryption.cookie("super_cookie").mask_ipv4(8).mask_ipv6(2);
+sp.auto_cookie_secure.enable();
diff --git a/src/tests/config/config_noncore_function_hooking.ini b/src/tests/config/config_noncore_function_hooking.ini
new file mode 100644
index 00000000..88f2acf9
--- /dev/null
+++ b/src/tests/config/config_noncore_function_hooking.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("custom_fun").drop();
diff --git a/src/tests/config/config_rand_harden_disabled.ini b/src/tests/config/config_rand_harden_disabled.ini
new file mode 100644
index 00000000..b9cd2277
--- /dev/null
+++ b/src/tests/config/config_rand_harden_disabled.ini
@@ -0,0 +1 @@
+sp.harden_random.disable();
diff --git a/src/tests/config/config_serialize.ini b/src/tests/config/config_serialize.ini
new file mode 100644
index 00000000..f2c1699f
--- /dev/null
+++ b/src/tests/config/config_serialize.ini
@@ -0,0 +1,2 @@
+sp.global.secret_key("abcdef");
+sp.unserialize_hmac.enable();
\ No newline at end of file
diff --git a/src/tests/config/config_serialize_sim.ini b/src/tests/config/config_serialize_sim.ini
new file mode 100644
index 00000000..7f015e07
--- /dev/null
+++ b/src/tests/config/config_serialize_sim.ini
@@ -0,0 +1,2 @@
+sp.global.secret_key("abcdef");
+sp.unserialize_hmac.enable().simulation();
diff --git a/src/tests/config/disable_xxe.ini b/src/tests/config/disable_xxe.ini
new file mode 100644
index 00000000..bc9d1f2e
--- /dev/null
+++ b/src/tests/config/disable_xxe.ini
@@ -0,0 +1 @@
+sp.disable_xxe.enable();
diff --git a/src/tests/config/disable_xxe_disable.ini b/src/tests/config/disable_xxe_disable.ini
new file mode 100644
index 00000000..bb1e432e
--- /dev/null
+++ b/src/tests/config/disable_xxe_disable.ini
@@ -0,0 +1 @@
+sp.disable_xxe.disable();
diff --git a/src/tests/config/disabled_function_local_var.ini b/src/tests/config/disabled_function_local_var.ini
new file mode 100644
index 00000000..64d98dcb
--- /dev/null
+++ b/src/tests/config/disabled_function_local_var.ini
@@ -0,0 +1,2 @@
+sp.disable_functions.function("phpinfo").var("b").value("1337").drop();
+sp.disable_functions.function("strlen").var("a").value("1337").drop();
diff --git a/src/tests/config/disabled_function_super_global_var.ini b/src/tests/config/disabled_function_super_global_var.ini
new file mode 100644
index 00000000..e0c87e14
--- /dev/null
+++ b/src/tests/config/disabled_function_super_global_var.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("strlen").var("_GET[bla]").value("test2").drop();
diff --git a/src/tests/config/disabled_functions.ini b/src/tests/config/disabled_functions.ini
new file mode 100644
index 00000000..cf54164d
--- /dev/null
+++ b/src/tests/config/disabled_functions.ini
@@ -0,0 +1,7 @@
+sp.disable_functions.function("system").drop();
+sp.disable_functions.function("vprintf").hash("123456789").drop();
+sp.disable_functions.function("printf").disable().drop();
+sp.disable_functions.function("printf").simulation().drop();
+sp.disable_functions.function("print").disable().drop(); # this is a comment
+sp.disable_functions.function_r("^var_dump$").drop();
+sp.disable_functions.function("sprintf").filename("wrong file name").drop();
diff --git a/src/tests/config/disabled_functions_cidr.ini b/src/tests/config/disabled_functions_cidr.ini
new file mode 100644
index 00000000..9e527bae
--- /dev/null
+++ b/src/tests/config/disabled_functions_cidr.ini
@@ -0,0 +1,4 @@
+sp.disable_functions.function("system").drop().cidr("127.0.0.1/8");
+sp.disable_functions.function("printf").drop().cidr("10.0.0.1/8");
+sp.disable_functions.function("strpos").drop().cidr("2001:0db8:0000:0000:0000:ff00:0042:8329/24");
+sp.disable_functions.function("printf").drop().cidr("2002:0db8:0000:0000:0000:ff00:0042:8329/24");
diff --git a/src/tests/config/disabled_functions_mb.ini b/src/tests/config/disabled_functions_mb.ini
new file mode 100644
index 00000000..b6afd97d
--- /dev/null
+++ b/src/tests/config/disabled_functions_mb.ini
@@ -0,0 +1,2 @@
+sp.disable_functions.function("strlen").drop();
+sp.disable_functions.function("mb_strlen").drop();
diff --git a/src/tests/config/disabled_functions_ret.ini b/src/tests/config/disabled_functions_ret.ini
new file mode 100644
index 00000000..2b769a9e
--- /dev/null
+++ b/src/tests/config/disabled_functions_ret.ini
@@ -0,0 +1,5 @@
+sp.disable_functions.function("testFunction").ret("0").drop().disable();
+sp.disable_functions.function("strpos").ret("0").drop().filename_r(".*\\.php");
+sp.disable_functions.function_r("str[ia]pos").ret_r("^[^a-z]+$").drop();
+sp.disable_functions.function_r("stripos").ret_r("^[^a-z]+").drop();
+sp.disable_functions.function("Bob::a").ret("0").drop();
diff --git a/src/tests/config/disabled_functions_ret_type.ini b/src/tests/config/disabled_functions_ret_type.ini
new file mode 100644
index 00000000..56c8e575
--- /dev/null
+++ b/src/tests/config/disabled_functions_ret_type.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("strpos").ret_type("false").drop().alias("Return value is FALSE");
diff --git a/src/tests/config/disabled_functions_ret_type_double.ini b/src/tests/config/disabled_functions_ret_type_double.ini
new file mode 100644
index 00000000..a1239d8f
--- /dev/null
+++ b/src/tests/config/disabled_functions_ret_type_double.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("cos").ret_type("double").drop().alias("Return value is a double");
diff --git a/src/tests/config/disabled_functions_ret_type_long.ini b/src/tests/config/disabled_functions_ret_type_long.ini
new file mode 100644
index 00000000..6cccd4d9
--- /dev/null
+++ b/src/tests/config/disabled_functions_ret_type_long.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("strlen").ret_type("long").drop().alias("Return value is a long");
diff --git a/src/tests/config/disabled_functions_ret_type_resource.ini b/src/tests/config/disabled_functions_ret_type_resource.ini
new file mode 100644
index 00000000..e81cf2ca
--- /dev/null
+++ b/src/tests/config/disabled_functions_ret_type_resource.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("fopen").ret_type("resource").drop().alias("Return value is a resource");
diff --git a/src/tests/config/disabled_functions_ret_type_str.ini b/src/tests/config/disabled_functions_ret_type_str.ini
new file mode 100644
index 00000000..b3ff0501
--- /dev/null
+++ b/src/tests/config/disabled_functions_ret_type_str.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("substr").ret_type("string").drop().alias("Return value is a string");
diff --git a/src/tests/config/disabled_functions_ret_type_true.ini b/src/tests/config/disabled_functions_ret_type_true.ini
new file mode 100644
index 00000000..02a37dd4
--- /dev/null
+++ b/src/tests/config/disabled_functions_ret_type_true.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("is_numeric").ret_type("true").drop().alias("Return value is a true");
diff --git a/src/tests/config/disabled_functions_retval.ini b/src/tests/config/disabled_functions_retval.ini
new file mode 100644
index 00000000..20422e4f
--- /dev/null
+++ b/src/tests/config/disabled_functions_retval.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("str_repeat").ret("fufufu").drop();
diff --git a/src/tests/config/disabled_functions_retval_rx.ini b/src/tests/config/disabled_functions_retval_rx.ini
new file mode 100644
index 00000000..ca2bce32
--- /dev/null
+++ b/src/tests/config/disabled_functions_retval_rx.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("str_repeat").ret_r("(fu){3}").drop();
diff --git a/src/tests/config/disabled_functions_zero_cidr.ini b/src/tests/config/disabled_functions_zero_cidr.ini
new file mode 100644
index 00000000..bba1af96
--- /dev/null
+++ b/src/tests/config/disabled_functions_zero_cidr.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").drop().cidr("0.0.0.0/0");
diff --git a/src/tests/config/dump_request.ini b/src/tests/config/dump_request.ini
new file mode 100644
index 00000000..8c595f97
--- /dev/null
+++ b/src/tests/config/dump_request.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").drop().dump("./dump_results/");
diff --git a/src/tests/config/dump_request_invalid_folder.ini b/src/tests/config/dump_request_invalid_folder.ini
new file mode 100644
index 00000000..b5ae1545
--- /dev/null
+++ b/src/tests/config/dump_request_invalid_folder.ini
@@ -0,0 +1 @@
+sp.disable_functions.function("system").drop().dump("/root/NON_EXISTENT/FOLDER/PLEASE/");
diff --git a/src/tests/config/empty.ini b/src/tests/config/empty.ini
new file mode 100644
index 00000000..e69de29b
diff --git a/src/tests/config/empty_conf.ini b/src/tests/config/empty_conf.ini
new file mode 100644
index 00000000..e69de29b
diff --git a/src/tests/config/encryption_key_only.ini b/src/tests/config/encryption_key_only.ini
new file mode 100644
index 00000000..7de44384
--- /dev/null
+++ b/src/tests/config/encryption_key_only.ini
@@ -0,0 +1 @@
+sp.global.secret_key("abcdef");
diff --git a/src/tests/config/global_strict.ini b/src/tests/config/global_strict.ini
new file mode 100644
index 00000000..2bc2bdc1
--- /dev/null
+++ b/src/tests/config/global_strict.ini
@@ -0,0 +1 @@
+ sp.global_strict.enable();
diff --git a/src/tests/config/global_strict_disabled.ini b/src/tests/config/global_strict_disabled.ini
new file mode 100644
index 00000000..2e68471f
--- /dev/null
+++ b/src/tests/config/global_strict_disabled.ini
@@ -0,0 +1 @@
+sp.global_strict.disable();
diff --git a/src/tests/config/harden_rand.ini b/src/tests/config/harden_rand.ini
new file mode 100644
index 00000000..89e19be4
--- /dev/null
+++ b/src/tests/config/harden_rand.ini
@@ -0,0 +1 @@
+sp.harden_random.enable();
diff --git a/src/tests/config/upload_validation.ini b/src/tests/config/upload_validation.ini
new file mode 100644
index 00000000..06461344
--- /dev/null
+++ b/src/tests/config/upload_validation.ini
@@ -0,0 +1,2 @@
+sp.upload_validation.script("tests/upload_ko.sh");
+sp.upload_validation.enable();
diff --git a/src/tests/config/upload_validation_invalid.ini b/src/tests/config/upload_validation_invalid.ini
new file mode 100644
index 00000000..7a638a15
--- /dev/null
+++ b/src/tests/config/upload_validation_invalid.ini
@@ -0,0 +1 @@
+sp.upload_validation.script("./tests/data/upload_invalid.sh").enable();
diff --git a/src/tests/config/upload_validation_ko.ini b/src/tests/config/upload_validation_ko.ini
new file mode 100644
index 00000000..b15977f7
--- /dev/null
+++ b/src/tests/config/upload_validation_ko.ini
@@ -0,0 +1 @@
+sp.upload_validation.script("./tests/data/upload_ko.sh").enable();
diff --git a/src/tests/config/upload_validation_ko_simulation.ini b/src/tests/config/upload_validation_ko_simulation.ini
new file mode 100644
index 00000000..da56439f
--- /dev/null
+++ b/src/tests/config/upload_validation_ko_simulation.ini
@@ -0,0 +1 @@
+sp.upload_validation.script("./tests/data/upload_ko.sh").enable().simulation();
diff --git a/src/tests/config/upload_validation_no_exist.ini b/src/tests/config/upload_validation_no_exist.ini
new file mode 100644
index 00000000..24f81a50
--- /dev/null
+++ b/src/tests/config/upload_validation_no_exist.ini
@@ -0,0 +1 @@
+sp.upload_validation.script("fufufufufu").enable();
diff --git a/src/tests/config/upload_validation_non_exec.ini b/src/tests/config/upload_validation_non_exec.ini
new file mode 100644
index 00000000..bdf0a57d
--- /dev/null
+++ b/src/tests/config/upload_validation_non_exec.ini
@@ -0,0 +1 @@
+sp.upload_validation.script("tests/data/upload_no_exec.sh").enable();
diff --git a/src/tests/config/upload_validation_ok.ini b/src/tests/config/upload_validation_ok.ini
new file mode 100644
index 00000000..5df8db8f
--- /dev/null
+++ b/src/tests/config/upload_validation_ok.ini
@@ -0,0 +1 @@
+sp.upload_validation.script("./tests/data/upload_ok.sh").enable();
diff --git a/src/tests/data/upload_invalid.sh b/src/tests/data/upload_invalid.sh
new file mode 100755
index 00000000..e5eb0c63
--- /dev/null
+++ b/src/tests/data/upload_invalid.sh
@@ -0,0 +1 @@
+lulz
diff --git a/src/tests/data/upload_ko.sh b/src/tests/data/upload_ko.sh
new file mode 100755
index 00000000..c4cacdc6
--- /dev/null
+++ b/src/tests/data/upload_ko.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exit 1;
diff --git a/src/tests/data/upload_no_exec.sh b/src/tests/data/upload_no_exec.sh
new file mode 100644
index 00000000..6b9cafae
--- /dev/null
+++ b/src/tests/data/upload_no_exec.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exit 0;
diff --git a/src/tests/data/upload_ok.sh b/src/tests/data/upload_ok.sh
new file mode 100755
index 00000000..6b9cafae
--- /dev/null
+++ b/src/tests/data/upload_ok.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exit 0;
diff --git a/src/tests/deny_writable_execution.phpt b/src/tests/deny_writable_execution.phpt
new file mode 100644
index 00000000..2870561a
--- /dev/null
+++ b/src/tests/deny_writable_execution.phpt
@@ -0,0 +1,44 @@
+--TEST--
+Readonly execution attempt
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disable_writable.ini
+--FILE--
+
+--EXPECTF--
+Code execution within a non-writable file.
+[snuffleupagus][0.0.0.0][readonly_exec][drop] Attempted execution of a writable file (%a/writable_file.txt).
+--CLEAN--
+
\ No newline at end of file
diff --git a/src/tests/deny_writable_execution_disabled.phpt b/src/tests/deny_writable_execution_disabled.phpt
new file mode 100644
index 00000000..6d1233bf
--- /dev/null
+++ b/src/tests/deny_writable_execution_disabled.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Readonly execution attempt
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disable_writable_disabled.ini
+--FILE--
+
+--EXPECT--
+Code execution within a writable file.
+Code execution within a non-writable file.
+--CLEAN--
+
\ No newline at end of file
diff --git a/src/tests/deny_writable_execution_simulation.phpt b/src/tests/deny_writable_execution_simulation.phpt
new file mode 100644
index 00000000..3278be89
--- /dev/null
+++ b/src/tests/deny_writable_execution_simulation.phpt
@@ -0,0 +1,45 @@
+--TEST--
+Readonly execution attempt (simulation mode)
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disable_writable_simulation.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][readonly_exec][notice] Attempted execution of a writable file (%a/writable_file.txt).
+Code execution within a writable file.
+Code execution within a non-writable file.
+--CLEAN--
+
\ No newline at end of file
diff --git a/src/tests/disable_xxe_dom.phpt b/src/tests/disable_xxe_dom.phpt
new file mode 100644
index 00000000..47f3db3b
--- /dev/null
+++ b/src/tests/disable_xxe_dom.phpt
@@ -0,0 +1,71 @@
+--TEST--
+Disable XXE
+--SKIPIF--
+
+--INI--
+extension=`php-config --extension-dir`/dom.so
+sp.configuration_file={PWD}/config/disable_xxe.ini
+--FILE--
+
+
+]>
+&foo;
+EOD;
+
+file_put_contents('content.xml', $xml);
+
+libxml_disable_entity_loader(true);
+$dom = new DOMDocument('1.0');
+$dom->loadXML($xml, LIBXML_DTDATTR|LIBXML_DTDLOAD|LIBXML_NOENT);
+printf("libxml_disable_entity to true: %s\n", $dom->getElementsByTagName('testing')->item(0)->nodeValue);
+
+libxml_disable_entity_loader(false);
+$dom = new DOMDocument('1.0');
+$dom->loadXML($xml, LIBXML_DTDATTR|LIBXML_DTDLOAD|LIBXML_NOENT);
+printf("libxml_disable_entity to false: %s\n", $dom->getElementsByTagName('testing')->item(0)->nodeValue);
+
+$xml = "foo";
+file_put_contents('content.xml', $xml);
+
+libxml_disable_entity_loader(false);
+$dom = new DOMDocument('1.0');
+$dom->loadXML($xml, LIBXML_DTDATTR|LIBXML_DTDLOAD|LIBXML_NOENT);
+printf("without xxe: %s", $dom->getElementsByTagName('testing')->item(0)->nodeValue);
+
+?>
+--EXPECTF--
+Warning: DOMDocument::loadXML(): I/O warning : failed to load external entity "file://%a/content.txt" in %a/disable_xxe_dom.php on line %d
+
+Warning: DOMDocument::loadXML(): Failure to process entity foo in Entity, line: %d in %a/disable_xxe_dom.php on line %d
+
+Warning: DOMDocument::loadXML(): Entity 'foo' not defined in Entity, line: %d in %a/disable_xxe_dom.php on line %d
+
+Notice: Trying to get property of non-object in %a/disable_xxe_dom.php on line %d
+libxml_disable_entity to true:
+
+Warning: DOMDocument::loadXML(): I/O warning : failed to load external entity "file://%a/content.txt" in %a/disable_xxe_dom.php on line %d
+
+Warning: DOMDocument::loadXML(): Failure to process entity foo in Entity, line: %d in %a/disable_xxe_dom.php on line %d
+
+Warning: DOMDocument::loadXML(): Entity 'foo' not defined in Entity, line: %d in %a/disable_xxe_dom.php on line %d
+
+Notice: Trying to get property of non-object in %a/disable_xxe_dom.php on line %d
+libxml_disable_entity to false:
+without xxe: foo
+--CLEAN--
+
diff --git a/src/tests/disable_xxe_dom_disabled.phpt b/src/tests/disable_xxe_dom_disabled.phpt
new file mode 100644
index 00000000..b89b595f
--- /dev/null
+++ b/src/tests/disable_xxe_dom_disabled.phpt
@@ -0,0 +1,56 @@
+--TEST--
+Disable XXE
+--SKIPIF--
+
+--INI--
+extension=`php-config --extension-dir`/dom.so
+sp.configuration_file={PWD}/config/disable_xxe_disable.ini
+--FILE--
+WARNING, external entity loaded!';
+file_put_contents($dir . '/content.txt', $content);
+
+$xml = <<
+
+]>
+&foo;
+EOD;
+
+file_put_contents($dir . '/content.xml', $xml);
+
+libxml_disable_entity_loader(true);
+$dom = new DOMDocument('1.0');
+$dom->loadXML($xml, LIBXML_DTDATTR|LIBXML_DTDLOAD|LIBXML_NOENT);
+printf("libxml_disable_entity to true: %s\n", $dom->getElementsByTagName('testing')->item(0)->nodeValue);
+
+libxml_disable_entity_loader(false);
+$dom = new DOMDocument('1.0');
+$dom->loadXML($xml, LIBXML_DTDATTR|LIBXML_DTDLOAD|LIBXML_NOENT);
+printf("libxml_disable_entity to false: %s\n", $dom->getElementsByTagName('testing')->item(0)->nodeValue);
+
+$xml = "foo";
+file_put_contents('content.xml', $xml);
+
+libxml_disable_entity_loader(false);
+$dom = new DOMDocument('1.0');
+$dom->loadXML($xml, LIBXML_DTDATTR|LIBXML_DTDLOAD|LIBXML_NOENT);
+printf("without xxe: %s", $dom->getElementsByTagName('testing')->item(0)->nodeValue);
+
+?>
+--EXPECTF--
+libxml_disable_entity to true: WARNING, external entity loaded!
+libxml_disable_entity to false: WARNING, external entity loaded!
+without xxe: foo
+--CLEAN--
+
diff --git a/src/tests/disable_xxe_simplexml.phpt b/src/tests/disable_xxe_simplexml.phpt
new file mode 100644
index 00000000..54404a34
--- /dev/null
+++ b/src/tests/disable_xxe_simplexml.phpt
@@ -0,0 +1,52 @@
+--TEST--
+Disable XXE
+--SKIPIF--
+
+--INI--
+extension=`php-config --extension-dir`/simplexml.so
+sp.configuration_file={PWD}/config/disable_xxe.ini
+--FILE--
+
+
+]>
+&foo;
+EOD;
+
+file_put_contents('content.xml', $xml);
+
+libxml_disable_entity_loader(true);
+$doc = new SimpleXMLElement($xml);
+printf("libxml_disable_entity to true: %s\n", $doc->testing);
+
+libxml_disable_entity_loader(false);
+$doc = new SimpleXMLElement($xml);
+printf("libxml_disable_entity to false: %s\n", $doc->testing);
+
+$xml = "foo";
+file_put_contents('content.xml', $xml);
+
+$doc = new SimpleXMLElement($xml);
+printf("without xxe: %s", $doc->testing);
+
+?>
+--EXPECT--
+libxml_disable_entity to true:
+libxml_disable_entity to false:
+without xxe: foo
+--CLEAN--
+
diff --git a/src/tests/disable_xxe_simplexml_oop.phpt b/src/tests/disable_xxe_simplexml_oop.phpt
new file mode 100644
index 00000000..62762eb1
--- /dev/null
+++ b/src/tests/disable_xxe_simplexml_oop.phpt
@@ -0,0 +1,52 @@
+--TEST--
+Disable XXE
+--SKIPIF--
+
+--INI--
+extension=`php-config --extension-dir`/simplexml.so
+sp.configuration_file={PWD}/config/disable_xxe.ini
+--FILE--
+
+
+]>
+&foo;
+EOD;
+
+file_put_contents('content.xml', $xml);
+
+libxml_disable_entity_loader(true);
+$doc = simplexml_load_string($xml);
+printf("libxml_disable_entity to true: %s\n", $doc->testing);
+
+libxml_disable_entity_loader(false);
+$doc = simplexml_load_string($xml);
+printf("libxml_disable_entity to false: %s\n", $doc->testing);
+
+$xml = "foo";
+file_put_contents('content.xml', $xml);
+
+$doc = simplexml_load_string($xml);
+printf("without xxe: %s", $doc->testing);
+
+?>
+--EXPECT--
+libxml_disable_entity to true:
+libxml_disable_entity to false:
+without xxe: foo
+--CLEAN--
+
diff --git a/src/tests/disable_xxe_xml_parse.phpt b/src/tests/disable_xxe_xml_parse.phpt
new file mode 100644
index 00000000..944bc383
--- /dev/null
+++ b/src/tests/disable_xxe_xml_parse.phpt
@@ -0,0 +1,104 @@
+--TEST--
+Disable XXE
+--SKIPIF--
+
+--INI--
+extension=`php-config --extension-dir`/xml.so
+sp.configuration_file={PWD}/config/disable_xxe.ini
+--FILE--
+
+
+]>
+&foo;
+EOD;
+
+file_put_contents('content.xml', $xml);
+
+function create_parser() {
+ $parser = xml_parser_create();
+ xml_set_element_handler(
+ $parser,
+ function($parser, $name, array $attributes) {
+ var_dump($name);
+ echo "\n";
+ var_dump($attributes);
+ },
+ function($parser, $name) {
+ var_dump($name);
+ }
+ );
+
+ xml_set_character_data_handler(
+ $parser,
+ function ($parser, $text){
+ echo 'text' . $text;
+ }
+ );
+
+ return $parser;
+}
+
+libxml_disable_entity_loader(true);
+$parser = create_parser();
+$doc = xml_parse($parser, $xml, true);
+xml_parser_free($parser);
+
+libxml_disable_entity_loader(false);
+$parser = create_parser();
+$doc = xml_parse($parser, $xml, true);
+xml_parser_free($parser);
+
+$xml = "foo";
+file_put_contents('content.xml', $xml);
+$parser = create_parser();
+$doc = xml_parse($parser, $xml, true);
+xml_parser_free($parser);
+
+--EXPECT--
+string(4) "TEST"
+
+array(0) {
+}
+string(7) "TESTING"
+
+array(0) {
+}
+string(7) "TESTING"
+string(4) "TEST"
+string(4) "TEST"
+
+array(0) {
+}
+string(7) "TESTING"
+
+array(0) {
+}
+string(7) "TESTING"
+string(4) "TEST"
+string(4) "TEST"
+
+array(0) {
+}
+string(7) "TESTING"
+
+array(0) {
+}
+textfoostring(7) "TESTING"
+string(4) "TEST"
+--CLEAN--
+
diff --git a/src/tests/disabled_function_local_var.phpt b/src/tests/disabled_function_local_var.phpt
new file mode 100644
index 00000000..3142039a
--- /dev/null
+++ b/src/tests/disabled_function_local_var.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Disable functions - match on a local variable
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_function_local_var.ini
+--FILE--
+
+--EXPECTF--
+Value of a: 1338
+2
+Value of a: 1337
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'strlen' in %a/tests/disabled_function_local_var.php:%d has been disabled.
\ No newline at end of file
diff --git a/src/tests/disabled_function_super_global_var.phpt b/src/tests/disabled_function_super_global_var.phpt
new file mode 100644
index 00000000..d41897a3
--- /dev/null
+++ b/src/tests/disabled_function_super_global_var.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Disable functions - match on a super global
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_function_super_global_var.ini
+--GET--
+bla=test
+--FILE--
+
+--EXPECTF--
+4
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'strlen' in %s/tests/disabled_function_super_global_var.php:%d has been disabled.
diff --git a/src/tests/disabled_functions.phpt b/src/tests/disabled_functions.phpt
new file mode 100644
index 00000000..37da911c
--- /dev/null
+++ b/src/tests/disabled_functions.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'system' in %a/tests/disabled_functions.php:%d has been disabled.
+[snuffleupagus][0.0.0.0][disabled_function][notice] The call to the function 'printf' in %a/tests/disabled_functions.php:%d has been disabled.
+printf in simulation mode
+print in disabled mode
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'var_dump' in %a/tests/disabled_functions.php:%d has been disabled.
+1
diff --git a/src/tests/disabled_functions_cidr.phpt b/src/tests/disabled_functions_cidr.phpt
new file mode 100644
index 00000000..5b131072
--- /dev/null
+++ b/src/tests/disabled_functions_cidr.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--ENV--
+return <<
+--EXPECTF--
+[snuffleupagus][127.0.0.1][disabled_function][drop] The call to the function 'system' in %a/tests/disabled_functions_cidr.php:2 has been disabled.
+1337
diff --git a/src/tests/disabled_functions_cidr_6.phpt b/src/tests/disabled_functions_cidr_6.phpt
new file mode 100644
index 00000000..f2c5f5a8
--- /dev/null
+++ b/src/tests/disabled_functions_cidr_6.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--ENV--
+return <<
+--EXPECTF--
+[snuffleupagus][2001:0db8:0000:0000:0000:ff00:0042:8328][disabled_function][drop] The call to the function 'strpos' in %a/tests/disabled_functions_cidr_6.php:2 has been disabled.
+1337
diff --git a/src/tests/disabled_functions_filename_r.phpt b/src/tests/disabled_functions_filename_r.phpt
new file mode 100644
index 00000000..ed468029
--- /dev/null
+++ b/src/tests/disabled_functions_filename_r.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Disable functions - filename regexp
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_filename_r.ini
+--FILE--
+
+--EXPECTF--
+42
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'shell_exec' in %a/tests/disabled_functions_filename_r.php:%d has been disabled.
\ No newline at end of file
diff --git a/src/tests/disabled_functions_mb.phpt b/src/tests/disabled_functions_mb.phpt
new file mode 100644
index 00000000..70890639
--- /dev/null
+++ b/src/tests/disabled_functions_mb.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_mb.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'strlen' in %a/tests/disabled_functions_mb.php:2 has been disabled.
diff --git a/src/tests/disabled_functions_method.phpt b/src/tests/disabled_functions_method.phpt
new file mode 100644
index 00000000..33651b7a
--- /dev/null
+++ b/src/tests/disabled_functions_method.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_method.ini
+--FILE--
+method1("pif");
+$c->method2("paf");
+$c->method3("pouet");
+?>
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'AwesomeClass::method1' in %a/tests/disabled_functions_method.php:4 has been disabled.
+method2:paf
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'AwesomeClass::method3' in %a/tests/disabled_functions_method.php:10 has been disabled, because its argument 'a' content (pouet) matched a rule.
diff --git a/src/tests/disabled_functions_name_r.phpt b/src/tests/disabled_functions_name_r.phpt
new file mode 100644
index 00000000..0e29abbb
--- /dev/null
+++ b/src/tests/disabled_functions_name_r.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_name_r.ini
+--FILE--
+
+--EXPECTF--
+42
+1337
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_name_r.php:3, because the return value (1337) of the function 'system' matched a rule.
diff --git a/src/tests/disabled_functions_name_type.phpt b/src/tests/disabled_functions_name_type.phpt
new file mode 100644
index 00000000..c5b24d62
--- /dev/null
+++ b/src/tests/disabled_functions_name_type.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_name_type.ini
+--FILE--
+
+--EXPECTF--
+0
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'strcmp' in %a/disabled_functions_name_type.php:%d has been disabled, because its argument 'str1' content (?) matched a rule.
diff --git a/src/tests/disabled_functions_namespace.phpt b/src/tests/disabled_functions_namespace.phpt
new file mode 100644
index 00000000..72c7d0be
--- /dev/null
+++ b/src/tests/disabled_functions_namespace.phpt
@@ -0,0 +1,31 @@
+--TEST--
+Disable functions: namespaces support isn't implemented now
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_namespace.ini
+--FILE--
+
+--XFAIL--
+--EXPECTF--
+[snuffleupagus] The call to the function 'strcmp' in %a/tests/disabled_functions_namespace.php:%d has been disabled.
diff --git a/src/tests/disabled_functions_noconf.phpt b/src/tests/disabled_functions_noconf.phpt
new file mode 100644
index 00000000..cb134137
--- /dev/null
+++ b/src/tests/disabled_functions_noconf.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/empty.ini
+--FILE--
+
+--EXPECT--
+1
diff --git a/src/tests/disabled_functions_nul_byte.phpt b/src/tests/disabled_functions_nul_byte.phpt
new file mode 100644
index 00000000..95e87de5
--- /dev/null
+++ b/src/tests/disabled_functions_nul_byte.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Disable functions with nul byte
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_nul_byte.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'system' in %a/tests/disabled_functions_nul_byte.php:2 has been disabled, because its argument 'command' content (0id) matched a rule.
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'system' in %a/tests/disabled_functions_nul_byte.php:3 has been disabled, because its argument 'command' content (id) matched a rule.
\ No newline at end of file
diff --git a/src/tests/disabled_functions_param.phpt b/src/tests/disabled_functions_param.phpt
new file mode 100644
index 00000000..23092172
--- /dev/null
+++ b/src/tests/disabled_functions_param.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_param.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'system' in %a/disabled_functions_param.php:2 has been disabled, because its argument 'command' content (id) matched the rule '1'.
+win
+int(15)
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'shell_exec' in %a/disabled_functions_param.php:5 has been disabled, because its argument 'cmd' content (id) matched the rule '3'.
+42
+[snuffleupagus][0.0.0.0][disabled_function][notice] The call to the function 'strcmp' in %a/tests/disabled_functions_param.php:7 has been disabled, because its argument 'str1' content (bla) matched the rule '5'.
+[snuffleupagus][0.0.0.0][disabled_function][notice] The call to the function 'strncmp' in %a/tests/disabled_functions_param.php:8 has been disabled, because its argument 'str1' content (bla) matched a rule.
diff --git a/src/tests/disabled_functions_param_alias.phpt b/src/tests/disabled_functions_param_alias.phpt
new file mode 100644
index 00000000..fe3d1c1a
--- /dev/null
+++ b/src/tests/disabled_functions_param_alias.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Disable functions - alias
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_param_alias.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'system' in %a/tests/disabled_functions_param_alias.php:2 has been disabled, because of the the rule '1'.
+[snuffleupagus][0.0.0.0][disabled_function][notice] The call to the function 'shell_exec' in %a/tests/disabled_functions_param_alias.php:3 has been disabled, because of the the rule '2'.
diff --git a/src/tests/disabled_functions_param_allow.phpt b/src/tests/disabled_functions_param_allow.phpt
new file mode 100644
index 00000000..b6ff01a5
--- /dev/null
+++ b/src/tests/disabled_functions_param_allow.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Disable functions - allow
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_param_allow.ini
+--FILE--
+
+--EXPECTF--
+win
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'system' in %a/tests/disabled_functions_param_allow.php:3 has been disabled.
\ No newline at end of file
diff --git a/src/tests/disabled_functions_param_array.phpt b/src/tests/disabled_functions_param_array.phpt
new file mode 100644
index 00000000..6596d1ac
--- /dev/null
+++ b/src/tests/disabled_functions_param_array.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_param_array.ini
+--FILE--
+"test1");
+foo($a);
+$a=Array("a"=>"abcd");
+foo($a);
+$a=Array("a"=>"abcde");
+foo($a);
+$a=Array("bla"=>"abcdef");
+foo($a);
+$a=Array("bla"=>"aaa", "a"=>"eee" );
+foo($a);
+$a=Array("test"=>"aaa", "a"=>"fff" );
+foo($a);
+$a=Array("test2"=>Array("foo"=>Array("lol"=>"bbb")), "a"=>"cccc");
+foo($a);
+$a=Array("test2"=>Array("foo"=>Array("lol"=>"aaa")), "a"=>"dddd");
+foo($a);
+?>
+--EXPECTF--
+test1
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'foo' in %a/disabled_functions_param_array.php:3 has been disabled, because its argument 'arr' content (Array) matched the rule '1'.
+abcde
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'foo' in %a/disabled_functions_param_array.php:3 has been disabled, because its argument 'arr' content (Array) matched the rule '2'.
+eee
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'foo' in %a/disabled_functions_param_array.php:3 has been disabled, because its argument 'arr' content (Array) matched the rule '3'.
+cccc
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'foo' in %a/disabled_functions_param_array.php:3 has been disabled, because its argument 'arr' content (Array) matched the rule '4'.
diff --git a/src/tests/disabled_functions_param_int.phpt b/src/tests/disabled_functions_param_int.phpt
new file mode 100644
index 00000000..3b2cc08c
--- /dev/null
+++ b/src/tests/disabled_functions_param_int.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_param_int.ini
+--FILE--
+
+--EXPECTF--
+1
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'foobar' in %a/tests/disabled_functions_param_int.php:3 has been disabled, because its argument 'id' content (42) matched a rule.
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'foobar' in %a/tests/disabled_functions_param_int.php:3 has been disabled, because its argument 'id' content (1337) matched a rule.
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'foobar' in %a/tests/disabled_functions_param_int.php:3 has been disabled, because its argument 'id' content (13374242) matched a rule.
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'foobar' in %a/tests/disabled_functions_param_int.php:3 has been disabled, because its argument 'id' content (42) matched a rule.
+10
diff --git a/src/tests/disabled_functions_param_r.phpt b/src/tests/disabled_functions_param_r.phpt
new file mode 100644
index 00000000..3708881e
--- /dev/null
+++ b/src/tests/disabled_functions_param_r.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_param_r.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'system' in %a/tests/disabled_functions_param_r.php:2 has been disabled, because its argument 'command' content (id) matched a rule.
+win
diff --git a/src/tests/disabled_functions_param_str_representation.phpt b/src/tests/disabled_functions_param_str_representation.phpt
new file mode 100644
index 00000000..7cbdc0f8
--- /dev/null
+++ b/src/tests/disabled_functions_param_str_representation.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Disable functions - casting various types to string internally
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_param_str_representation.ini
+--FILE--
+
+--EXPECTF--
+true
+false
+NULL
+1
+1.0
+123
diff --git a/src/tests/disabled_functions_parse_class.phpt b/src/tests/disabled_functions_parse_class.phpt
new file mode 100644
index 00000000..af9ed888
--- /dev/null
+++ b/src/tests/disabled_functions_parse_class.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Disable functions - Parsing of an Object as a return value of a function
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_ret.ini
+--FILE--
+a() instanceof StdClass)?'Y':'N';
+?>
+--EXPECT--
+Y
diff --git a/src/tests/disabled_functions_require.phpt b/src/tests/disabled_functions_require.phpt
new file mode 100644
index 00000000..1eedde42
--- /dev/null
+++ b/src/tests/disabled_functions_require.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Disable functions - Require
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_require.ini
+--FILE--
+
+--XFAIL--
+PHP doesn't replace the format string, so the test is failing.
+--EXPECTF--
+[snuffleupagus][0.0.0.0][include][drop] Inclusion of a forbidden file (%a/test.bla)
+--CLEAN--
+
diff --git a/src/tests/disabled_functions_ret.phpt b/src/tests/disabled_functions_ret.phpt
new file mode 100644
index 00000000..b64bf70a
--- /dev/null
+++ b/src/tests/disabled_functions_ret.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Disable functions check on `ret`.
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_ret.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_ret.php:2, because the return value (0) of the function 'strpos' matched a rule.
diff --git a/src/tests/disabled_functions_ret2.phpt b/src/tests/disabled_functions_ret2.phpt
new file mode 100644
index 00000000..b713201d
--- /dev/null
+++ b/src/tests/disabled_functions_ret2.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Disable functions check on `ret`.
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_ret.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_ret2.php:2, because the return value (0) of the function 'stripos' matched a rule.
diff --git a/src/tests/disabled_functions_ret3.phpt b/src/tests/disabled_functions_ret3.phpt
new file mode 100644
index 00000000..d5f96d03
--- /dev/null
+++ b/src/tests/disabled_functions_ret3.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Disable functions check on `ret`.
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_ret.ini
+--FILE--
+a() . ".\n";
+echo("We're at the end of the execution.\n");
+?>
+--EXPECTF--
+We're in function `a`.
+`a` returned: 1.
+We're at the end of the execution.
\ No newline at end of file
diff --git a/src/tests/disabled_functions_ret_allow.phpt b/src/tests/disabled_functions_ret_allow.phpt
new file mode 100644
index 00000000..16909955
--- /dev/null
+++ b/src/tests/disabled_functions_ret_allow.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Disable functions check on `ret`.
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_ret_allow.ini
+--FILE--
+
+--EXPECT--
+00
\ No newline at end of file
diff --git a/src/tests/disabled_functions_ret_allow_value.phpt b/src/tests/disabled_functions_ret_allow_value.phpt
new file mode 100644
index 00000000..881a0069
--- /dev/null
+++ b/src/tests/disabled_functions_ret_allow_value.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Disable functions check on `ret` allowed
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_ret_allow_value.ini
+--FILE--
+
+--EXPECT--
+0
diff --git a/src/tests/disabled_functions_ret_right_hash.phpt b/src/tests/disabled_functions_ret_right_hash.phpt
new file mode 100644
index 00000000..e0d8b5b1
--- /dev/null
+++ b/src/tests/disabled_functions_ret_right_hash.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_ret_right_hash.ini
+--FILE--
+
+--EXPECTF--
+1337
diff --git a/src/tests/disabled_functions_ret_simulation.phpt b/src/tests/disabled_functions_ret_simulation.phpt
new file mode 100644
index 00000000..58af3a9c
--- /dev/null
+++ b/src/tests/disabled_functions_ret_simulation.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Disable functions check on `ret` simulation
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_ret_simulation.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][notice] The execution has been aborted in %a/disabled_functions_ret_simulation.php:2, because the return value (0) of the function 'strpos' matched a rule.
+0
+[snuffleupagus][0.0.0.0][disabled_function][notice] The execution has been aborted in %a/disabled_functions_ret_simulation.php:3, because the function 'stripos' returned '0', which matched the rule '1'.
+0
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_ret_simulation.php:4, because the return value (0) of the function 'strcmp' matched a rule.
diff --git a/src/tests/disabled_functions_ret_type.phpt b/src/tests/disabled_functions_ret_type.phpt
new file mode 100644
index 00000000..f1c6e4c9
--- /dev/null
+++ b/src/tests/disabled_functions_ret_type.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Disable functions check on `ret` by type matching on boolean
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_ret_type.ini
+--FILE--
+
+--EXPECTF--
+0
+1337
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/tests/disabled_functions_ret_type.php:%d, because the function 'strpos' returned 'FALSE', which matched the rule 'Return value is FALSE'.
diff --git a/src/tests/disabled_functions_ret_type_double.phpt b/src/tests/disabled_functions_ret_type_double.phpt
new file mode 100644
index 00000000..b7942e1b
--- /dev/null
+++ b/src/tests/disabled_functions_ret_type_double.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Disable functions check on `ret` by type matching (double).
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_ret_type_double.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_ret_type_double.php:%d, because the function 'cos' returned '0.877583', which matched the rule 'Return value is a double'.
diff --git a/src/tests/disabled_functions_ret_type_long.phpt b/src/tests/disabled_functions_ret_type_long.phpt
new file mode 100644
index 00000000..b841c64c
--- /dev/null
+++ b/src/tests/disabled_functions_ret_type_long.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Disable functions check on `ret` by type matching (long).
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_ret_type_long.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_ret_type_long.php:%d, because the function 'strlen' returned '5', which matched the rule 'Return value is a long'.
diff --git a/src/tests/disabled_functions_ret_type_resource.phpt b/src/tests/disabled_functions_ret_type_resource.phpt
new file mode 100644
index 00000000..4ceb6105
--- /dev/null
+++ b/src/tests/disabled_functions_ret_type_resource.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Disable functions check on `ret` by type matching (resource).
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_ret_type_resource.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_ret_type_resource.php:2, because the function 'fopen' returned 'RESOURCE', which matched the rule 'Return value is a resource'.
diff --git a/src/tests/disabled_functions_ret_type_str.phpt b/src/tests/disabled_functions_ret_type_str.phpt
new file mode 100644
index 00000000..8c48b1da
--- /dev/null
+++ b/src/tests/disabled_functions_ret_type_str.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Disable functions check on `ret` by type matching (string).
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_ret_type_str.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_ret_type_str.php:%d, because the function 'substr' returned 'et', which matched the rule 'Return value is a string'.
diff --git a/src/tests/disabled_functions_ret_type_true.phpt b/src/tests/disabled_functions_ret_type_true.phpt
new file mode 100644
index 00000000..a5eae387
--- /dev/null
+++ b/src/tests/disabled_functions_ret_type_true.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Disable functions check on `ret` by type matching (true).
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_ret_type_true.ini
+--FILE--
+
+--EXPECTF--
+bool(false)
+1337
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_ret_type_true.php:%d, because the function 'is_numeric' returned 'TRUE', which matched the rule 'Return value is a true'.
diff --git a/src/tests/disabled_functions_ret_val.phpt b/src/tests/disabled_functions_ret_val.phpt
new file mode 100644
index 00000000..8a02b297
--- /dev/null
+++ b/src/tests/disabled_functions_ret_val.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Disable functions ret val
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_retval.ini
+--FILE--
+
+--EXPECTF--
+fufu
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_ret_val.php:3, because the return value (fufufu) of the function 'str_repeat' matched a rule.
diff --git a/src/tests/disabled_functions_ret_val_rx.phpt b/src/tests/disabled_functions_ret_val_rx.phpt
new file mode 100644
index 00000000..1054b706
--- /dev/null
+++ b/src/tests/disabled_functions_ret_val_rx.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Disable functions ret val rx
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/disabled_functions_retval_rx.ini
+--FILE--
+
+--EXPECTF--
+fufu
+[snuffleupagus][0.0.0.0][disabled_function][drop] The execution has been aborted in %a/disabled_functions_ret_val_rx.php:3, because the return value (fufufu) of the function 'str_repeat' matched a rule.
diff --git a/src/tests/disabled_functions_right_hash.phpt b/src/tests/disabled_functions_right_hash.phpt
new file mode 100644
index 00000000..f3c5fb31
--- /dev/null
+++ b/src/tests/disabled_functions_right_hash.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_right_hash.ini
+--FILE--
+
+--EXPECTF--
+1337
diff --git a/src/tests/disabled_functions_runtime.phpt b/src/tests/disabled_functions_runtime.phpt
new file mode 100644
index 00000000..1c6a1415
--- /dev/null
+++ b/src/tests/disabled_functions_runtime.phpt
@@ -0,0 +1,31 @@
+--TEST--
+Disable functions - runtime inclusion
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_functions_param_runtime.ini
+--FILE--
+
+--EXPECTF--
+1338
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'test' in %a has been disabled, because its argument 'param' content (1337) matched a rule.
+--CLEAN--
+
diff --git a/src/tests/disabled_functions_zero_cidr.phpt b/src/tests/disabled_functions_zero_cidr.phpt
new file mode 100644
index 00000000..35d187a9
--- /dev/null
+++ b/src/tests/disabled_functions_zero_cidr.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Disable functions
+--SKIPIF--
+
+--ENV--
+return <<
+--EXPECTF--
+[snuffleupagus][127.0.0.1][disabled_function][drop] The call to the function 'system' in %a/tests/disabled_functions_zero_cidr.php:2 has been disabled.
+1337
diff --git a/src/tests/disabled_option.phpt b/src/tests/disabled_option.phpt
new file mode 100644
index 00000000..8bc7e399
--- /dev/null
+++ b/src/tests/disabled_option.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Harden rand
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_rand_harden_disabled.ini
+--FILE--
+
+--EXPECT--
+84
+84
diff --git a/src/tests/disabled_user_functions.phpt b/src/tests/disabled_user_functions.phpt
new file mode 100644
index 00000000..8952d43e
--- /dev/null
+++ b/src/tests/disabled_user_functions.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Disabled user-created functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_disabled_user_functions.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'my_super_function' in %a/tests/disabled_user_functions.php:3 has been disabled.
diff --git a/src/tests/dump_request.phpt b/src/tests/dump_request.phpt
new file mode 100644
index 00000000..a752def7
--- /dev/null
+++ b/src/tests/dump_request.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Dump request
+--SKIPIF--
+
+--POST--
+post_a=data_post_a&post_b=data_post_b
+--GET--
+get_a=data_get_a&get_b=data_get_b
+--COOKIE--
+cookie_a=data_cookie_a&cookie_b=data_cookie_b
+--INI--
+sp.configuration_file={PWD}/config/dump_request.ini
+--FILE--
+
+--EXPECTF--
+1
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'system' in %a/dump_request.php:%d has been disabled.
diff --git a/src/tests/dump_request_invalid_folder.phpt b/src/tests/dump_request_invalid_folder.phpt
new file mode 100644
index 00000000..b866f700
--- /dev/null
+++ b/src/tests/dump_request_invalid_folder.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Dump request - invalid folder.
+--SKIPIF--
+
+--POST--
+post_a=data_post_a&post_b=data_post_b
+--GET--
+get_a=data_get_a&get_b=data_get_b
+--COOKIE--
+cookie_a=data_cookie_a&cookie_b=data_cookie_b
+--INI--
+sp.configuration_file={PWD}/config/dump_request_invalid_folder.ini
+--FILE--
+
+--EXPECTF--
+1
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'system' in %atests/dump_request_invalid_folder.php:3 has been disabled.
+[snuffleupagus][0.0.0.0][request_logging][error] Unable to open /root/NON_EXISTENT/FOLDER/PLEASE/sp_dump_%a_0.0.0.0.dump
+2
\ No newline at end of file
diff --git a/src/tests/dump_request_too_big.phpt b/src/tests/dump_request_too_big.phpt
new file mode 100644
index 00000000..81eb71c7
--- /dev/null
+++ b/src/tests/dump_request_too_big.phpt
@@ -0,0 +1,42 @@
+--TEST--
+Dump request -- to big, so it's truncated.
+--SKIPIF--
+
+--POST--
+post_a=data_post_a&post_b=data_post_b&post_c=c
+--GET--
+get_a=data_get_a&get_b=data_get_b&get_c=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaBBBB
+--COOKIE--
+cookie_a=data_cookie_a&cookie_b=data_cookie_b&data_cookie_c=cookie_c
+--ENV--
+return <<
+--EXPECTF--
+1
+[snuffleupagus][127.0.0.1][disabled_function][drop] The call to the function 'system' in %a/dump_request_too_big.php:%d has been disabled.
diff --git a/src/tests/empty_conf.phpt b/src/tests/empty_conf.phpt
new file mode 100644
index 00000000..411c817f
--- /dev/null
+++ b/src/tests/empty_conf.phpt
@@ -0,0 +1,8 @@
+--TEST--
+Empty configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/empty_conf.ini
+--FILE--
+--EXPECT--
diff --git a/src/tests/encrypt_cookies.phpt b/src/tests/encrypt_cookies.phpt
new file mode 100644
index 00000000..f8bf64f7
--- /dev/null
+++ b/src/tests/encrypt_cookies.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Cookie decryption in ipv4
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_encrypted_cookies.ini
+--COOKIE--
+super_cookie=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEmXkk3H0xheoOMxoWPEDw1Zd8NAmD9KbB2DSjQ=%3d;awful_cookie=awful_cookie_value;
+--ENV--
+return <<
+--EXPECT--
+array(2) {
+ ["super_cookie"]=>
+ string(11) "super_value"
+ ["awful_cookie"]=>
+ string(18) "awful_cookie_value"
+}
diff --git a/src/tests/encrypt_cookies2.phpt b/src/tests/encrypt_cookies2.phpt
new file mode 100644
index 00000000..be4c9903
--- /dev/null
+++ b/src/tests/encrypt_cookies2.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Cookie encryption in ipv4
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_encrypted_cookies.ini
+--COOKIE--
+--ENV--
+return <<
+--EXPECT--
+array(0) {
+}
diff --git a/src/tests/encrypt_cookies3.phpt b/src/tests/encrypt_cookies3.phpt
new file mode 100644
index 00000000..c85c5dca
--- /dev/null
+++ b/src/tests/encrypt_cookies3.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Cookie decryption with ipv6
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_encrypted_cookies.ini
+--COOKIE--
+super_cookie=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJNTUge7MpiVNi4q3DqstbcumllXBir0CbIQiDI%3D;awful_cookie=awful_cookie_value;
+--ENV--
+return <<
+--EXPECT--
+array(2) {
+ ["super_cookie"]=>
+ string(11) "super_value"
+ ["awful_cookie"]=>
+ string(18) "awful_cookie_value"
+}
diff --git a/src/tests/encrypt_cookies4.phpt b/src/tests/encrypt_cookies4.phpt
new file mode 100644
index 00000000..14d737ad
--- /dev/null
+++ b/src/tests/encrypt_cookies4.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Cookie encryption in ipv6
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_encrypted_cookies.ini
+--COOKIE--
+--ENV--
+return <<
+--EXPECT--
+array(0) {
+}
diff --git a/src/tests/encrypt_cookies_invalid_decryption.phpt b/src/tests/encrypt_cookies_invalid_decryption.phpt
new file mode 100644
index 00000000..a5187c1c
--- /dev/null
+++ b/src/tests/encrypt_cookies_invalid_decryption.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Cookie encryption
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_encrypted_cookies.ini
+display_errors=1
+display_startup_errors=1
+error_reporting=E_ALL
+--COOKIE--
+super_cookie=jWjORGsgZyqzk3WA63XZBmUoSknXWnXDfAAAAAAAAAAAAAAAAAAAAAA7LiMDfkpP94jDnMVH%2Fm41GeL0Y00q3mbOFYz%2FS9mQGySu;awful_cookie=awful_cookie_value;
+--ENV--
+return <<
+--EXPECT--
+
+array(1) {
+ ["awful_cookie"]=>
+ string(18) "awful_cookie_value"
+}
diff --git a/src/tests/encrypt_cookies_invalid_decryption2.phpt b/src/tests/encrypt_cookies_invalid_decryption2.phpt
new file mode 100644
index 00000000..f18cf6df
--- /dev/null
+++ b/src/tests/encrypt_cookies_invalid_decryption2.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Cookie encryption
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_encrypted_cookies.ini
+display_errors=1
+display_startup_errors=1
+error_reporting=E_ALL
+--COOKIE--
+super_cookie=1337;awful_cookie=awful_cookie_value;
+--ENV--
+return <<
+--EXPECT--
+
+array(1) {
+ ["awful_cookie"]=>
+ string(18) "awful_cookie_value"
+}
diff --git a/src/tests/encrypt_cookies_invalid_decryption3.phpt b/src/tests/encrypt_cookies_invalid_decryption3.phpt
new file mode 100644
index 00000000..f4afc325
--- /dev/null
+++ b/src/tests/encrypt_cookies_invalid_decryption3.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Cookie encryption
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_encrypted_cookies.ini
+--COOKIE--
+super_cookie=;awful_cookie=awful_cookie_value;
+--ENV--
+return <<
+--EXPECT--
+array(2) {
+ ["super_cookie"]=>
+ string(0) ""
+ ["awful_cookie"]=>
+ string(18) "awful_cookie_value"
+}
diff --git a/src/tests/encryption_key_only.phpt b/src/tests/encryption_key_only.phpt
new file mode 100644
index 00000000..bf5edb58
--- /dev/null
+++ b/src/tests/encryption_key_only.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Encryption key only
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/encryption_key_only.ini
+--FILE--
+
+--EXPECT--
+1337
+
diff --git a/src/tests/example_configuration.phpt b/src/tests/example_configuration.phpt
new file mode 100644
index 00000000..0bbf59c6
--- /dev/null
+++ b/src/tests/example_configuration.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Shipped configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/../../config/examples.ini
+--FILE--
+
+--EXPECTF--
+0
diff --git a/src/tests/global_strict.phpt b/src/tests/global_strict.phpt
new file mode 100644
index 00000000..e06721c5
--- /dev/null
+++ b/src/tests/global_strict.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Global strict mode
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/global_strict.ini
+--FILE--
+
+--EXPECTF--
+Fatal error: Uncaught TypeError: strcmp() expects parameter 2 to be string, array given in %a/global_strict.php:2
+Stack trace:
+#0 %a/global_strict.php(2): strcmp('pouet', Array)
+#1 {main}
+ thrown in %a/global_strict.php on line 2
diff --git a/src/tests/global_strict_disabled.phpt b/src/tests/global_strict_disabled.phpt
new file mode 100644
index 00000000..ca3ddfa6
--- /dev/null
+++ b/src/tests/global_strict_disabled.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Global strict mode
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/global_strict_disabled.ini
+--FILE--
+
+--EXPECTF--
+Warning: strcmp() expects parameter 2 to be string, array given in %a/global_strict_disabled.php on line 2
+1337
diff --git a/src/tests/harden_mt_rand.phpt b/src/tests/harden_mt_rand.phpt
new file mode 100644
index 00000000..8887613a
--- /dev/null
+++ b/src/tests/harden_mt_rand.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Harden mt_rand
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/harden_rand.ini
+--FILE--
+
+--EXPECT--
+win
diff --git a/src/tests/harden_rand.phpt b/src/tests/harden_rand.phpt
new file mode 100644
index 00000000..391bccc3
--- /dev/null
+++ b/src/tests/harden_rand.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Harden rand
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/harden_rand.ini
+--FILE--
+
+--EXPECT--
+win
diff --git a/src/tests/harden_rand_noargs.phpt b/src/tests/harden_rand_noargs.phpt
new file mode 100644
index 00000000..643a4538
--- /dev/null
+++ b/src/tests/harden_rand_noargs.phpt
@@ -0,0 +1,62 @@
+--TEST--
+Harden rand without any arguments
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/harden_rand.ini
+We should fix this
+--FILE--
+
+--EXPECTF--
+Warning: rand() expects exactly 2 parameters, 1 given in %s/tests/harden_rand_noargs.php on line %d
+
+Warning: mt_rand() expects exactly 2 parameters, 1 given in %s/tests/harden_rand_noargs.php on line %d
+
+Warning: mt_rand(): max(1) is smaller than min(2) in %s/tests/harden_rand_noargs.php on line %d
+
+Warning: rand() expects exactly 2 parameters, 3 given in %s/tests/harden_rand_noargs.php on line %d
+
+Warning: mt_rand() expects exactly 2 parameters, 3 given in %s/tests/harden_rand_noargs.php on line %d
+
+Warning: rand() expects parameter 1 to be integer, string given in %s/tests/harden_rand_noargs.php on line %d
+
+Warning: mt_rand() expects parameter 1 to be integer, string given in %s/tests/harden_rand_noargs.php on line %d
+
+Warning: rand() expects parameter 2 to be integer, string given in %s/tests/harden_rand_noargs.php on line %d
+
+Warning: mt_rand() expects parameter 2 to be integer, string given in %s/tests/harden_rand_noargs.php on line %d
+
+Warning: rand() expects exactly 2 parameters, 3 given in %s/tests/harden_rand_noargs.php on line %d
+
+Warning: mt_rand() expects exactly 2 parameters, 3 given in %s/tests/harden_rand_noargs.php on line %d
+Everything is fine
+Absolutely everything
+Even with single quotes
diff --git a/src/tests/inexistent_conf_file.phpt b/src/tests/inexistent_conf_file.phpt
new file mode 100644
index 00000000..c7c3fcdc
--- /dev/null
+++ b/src/tests/inexistent_conf_file.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Check for snuffleupagus presence
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/unexistent_configuration_file.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][config][error] Could not open configuration file %a/tests/config/unexistent_configuration_file.ini : No such file or directory
diff --git a/src/tests/loading.phpt b/src/tests/loading.phpt
new file mode 100644
index 00000000..25e2e170
--- /dev/null
+++ b/src/tests/loading.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Check for snuffleupagus presence
+--SKIPIF--
+
+--FILE--
+
+--EXPECT--
+snuffleupagus extension is available
diff --git a/src/tests/noncore_function_hooking.phpt b/src/tests/noncore_function_hooking.phpt
new file mode 100644
index 00000000..106123c0
--- /dev/null
+++ b/src/tests/noncore_function_hooking.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Hooking of user-defined functions
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_noncore_function_hooking.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][disabled_function][drop] The call to the function 'custom_fun' in %a/tests/noncore_function_hooking.php:3 has been disabled.
diff --git a/src/tests/phpinfo_presence.phpt b/src/tests/phpinfo_presence.phpt
new file mode 100644
index 00000000..35ed0ed1
--- /dev/null
+++ b/src/tests/phpinfo_presence.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Unserialize fail
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_serialize.ini
+--FILE--
+
+--EXPECT--
+1
diff --git a/src/tests/serialize.phpt b/src/tests/serialize.phpt
new file mode 100644
index 00000000..e93dbaf8
--- /dev/null
+++ b/src/tests/serialize.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Test serialize hmac
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_serialize.ini
+--FILE--
+
+--EXPECT--
+s:1:"a";650609b417904d0d9bbf1fc44a975d13ecdf6b02b715c1a06271fb3b673f25b1
+
diff --git a/src/tests/setcookie.phpt b/src/tests/setcookie.phpt
new file mode 100644
index 00000000..ba1d1c1f
--- /dev/null
+++ b/src/tests/setcookie.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Set cookies.
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_encrypted_cookies.ini
+--COOKIE--
+--ENV--
+return <<
+--EXPECTF--
+Warning: setcookie() expects at most 7 parameters, 8 given in %a/setcookie.php on line %d
+
+Warning: setcookie() expects at least 1 parameter, 0 given in %a/setcookie.php on line %d
+1337
diff --git a/src/tests/shipped_configuration.phpt b/src/tests/shipped_configuration.phpt
new file mode 100644
index 00000000..c060a852
--- /dev/null
+++ b/src/tests/shipped_configuration.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Shipped configuration
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/../../config/default.ini
+--FILE--
+
+--EXPECTF--
+0
diff --git a/src/tests/unserialize.phpt b/src/tests/unserialize.phpt
new file mode 100644
index 00000000..b1db9155
--- /dev/null
+++ b/src/tests/unserialize.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Unserialize ok
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_serialize.ini
+--FILE--
+
+--EXPECT--
+string(1) "a"
diff --git a/src/tests/unserialize_fail.phpt b/src/tests/unserialize_fail.phpt
new file mode 100644
index 00000000..5c0bb807
--- /dev/null
+++ b/src/tests/unserialize_fail.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Unserialize fail
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_serialize.ini
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][unserialize][drop] The serialized object is too small.
+bool(false)
+[snuffleupagus][0.0.0.0][unserialize][drop] Invalid HMAC for s:1:"a";alyualskdufyhalkdjsfh
+NULL
+[snuffleupagus][0.0.0.0][unserialize][drop] The serialized object is too small.
+bool(false)
+
+Warning: unserialize() expects at most 2 parameters, 4 given in %a/tests/unserialize_fail.php on line %d
+bool(false)
\ No newline at end of file
diff --git a/src/tests/unserialize_sim.phpt b/src/tests/unserialize_sim.phpt
new file mode 100644
index 00000000..8ebf64d8
--- /dev/null
+++ b/src/tests/unserialize_sim.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Unserialize ok
+--SKIPIF--
+
+--INI--
+sp.configuration_file={PWD}/config/config_serialize_sim.ini
+--FILE--
+
+--EXPECT--
+s:1:"a";650609b417904d0d9bbf1fc44a975d13ecdf6b02b715c1a06271fb3b673f25b1string(1) "a"
+[snuffleupagus][0.0.0.0][unserialize][notice] Invalid HMAC for s:1:"a";alyualskdufyhalkdjsfh
+string(1) "a"
diff --git a/src/tests/upload_validation.phpt b/src/tests/upload_validation.phpt
new file mode 100644
index 00000000..c802c162
--- /dev/null
+++ b/src/tests/upload_validation.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Upload a file, validation ok, no simulation
+--INI--
+file_uploads=1
+sp.configuration_file={PWD}/config/upload_validation.ini
+--POST_RAW--
+Content-Type: multipart/form-data; boundary=blabla
+--blabla
+Content-Disposition: form-data; name="test"; filename="test.php"
+--blabla--
+--FILE--
+
+--EXPECTF--
+1
diff --git a/src/tests/upload_validation_invalid.phpt b/src/tests/upload_validation_invalid.phpt
new file mode 100644
index 00000000..f8c993b9
--- /dev/null
+++ b/src/tests/upload_validation_invalid.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Upload a file, invalid validation script
+--INI--
+file_uploads=1
+sp.configuration_file={PWD}/config/upload_validation_invalid.ini
+--POST_RAW--
+Content-Type: multipart/form-data; boundary=blabla
+--blabla
+Content-Disposition: form-data; name="test"; filename="test.php"
+--blabla--
+--FILE--
+
+--EXPECTF--
+[snuffleupagus][0.0.0.0][upload_validation][error] Could not call './tests/data/upload_invalid.sh' : Exec format error
+[snuffleupagus][0.0.0.0][upload_valiation][drop] The upload of test.php on ? was rejected.
diff --git a/src/tests/upload_validation_ko.phpt b/src/tests/upload_validation_ko.phpt
new file mode 100644
index 00000000..cf4057ae
--- /dev/null
+++ b/src/tests/upload_validation_ko.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Upload a file, validation ko, no simulation
+--INI--
+file_uploads=1
+sp.configuration_file={PWD}/config/upload_validation_ko.ini
+output_buffering=off
+--POST_RAW--
+Content-Type: multipart/form-data; boundary=blabla
+--blabla
+Content-Disposition: form-data; name="test"; filename="test.php"
+--blabla--
+--FILE--
+--EXPECTF--
+[snuffleupagus][0.0.0.0][upload_valiation][drop] The upload of test.php on ? was rejected.
diff --git a/src/tests/upload_validation_no_exec.phpt b/src/tests/upload_validation_no_exec.phpt
new file mode 100644
index 00000000..90a58da5
--- /dev/null
+++ b/src/tests/upload_validation_no_exec.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Upload a file, validation script not executable
+--INI--
+file_uploads=1
+sp.configuration_file={PWD}/config/upload_validation_non_exec.ini
+output_buffering=off
+--POST_RAW--
+Content-Type: multipart/form-data; boundary=blabla
+--blabla
+Content-Disposition: form-data; name="test"; filename="test.php"
+--blabla--
+--FILE--
+
+--EXPECTF--
+array(1) {
+ ["test"]=>
+ array(5) {
+ ["name"]=>
+ string(8) "test.php"
+ ["type"]=>
+ string(0) ""
+ ["tmp_name"]=>
+ string(0) ""
+ ["error"]=>
+ int(3)
+ ["size"]=>
+ int(0)
+ }
+}
diff --git a/src/tests/upload_validation_nocrash.phpt b/src/tests/upload_validation_nocrash.phpt
new file mode 100644
index 00000000..6fa50d0a
--- /dev/null
+++ b/src/tests/upload_validation_nocrash.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Upload validation isn't crashing
+--INI--
+file_uploads=1
+sp.configuration_file={PWD}/config/upload_validation_ok.ini
+output_buffering=off
+--FILE--
+
+--EXPECTF--
+1
diff --git a/src/tests/upload_validation_ok.phpt b/src/tests/upload_validation_ok.phpt
new file mode 100644
index 00000000..f9b50150
--- /dev/null
+++ b/src/tests/upload_validation_ok.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Upload a file, validation ok, no simulation
+--INI--
+file_uploads=1
+sp.configuration_file={PWD}/config/upload_validation_ok.ini
+output_buffering=off
+--POST_RAW--
+Content-Type: multipart/form-data; boundary=blabla
+--blabla
+Content-Disposition: form-data; name="test"; filename="test.php"
+--blabla--
+--FILE--
+
+--EXPECTF--
+1
diff --git a/src/tweetnacl.c b/src/tweetnacl.c
new file mode 100644
index 00000000..937e879e
--- /dev/null
+++ b/src/tweetnacl.c
@@ -0,0 +1,842 @@
+#include "tweetnacl.h"
+#define FOR(i,n) for (i = 0;i < n;++i)
+#define sv static void
+
+typedef unsigned char u8;
+typedef unsigned long u32;
+typedef unsigned long long u64;
+typedef long long i64;
+typedef i64 gf[16];
+
+
+/* it's really stupid that there isn't a syscall for this */
+
+static int fd = -1;
+
+void randombytes(unsigned char *x,unsigned long long xlen)
+{
+ int i;
+
+ if (fd == -1) {
+ for (;;) {
+ fd = open("/dev/urandom",O_RDONLY);
+ if (fd != -1) break;
+ sleep(1);
+ }
+ }
+
+ while (xlen > 0) {
+ if (xlen < 1048576) i = xlen; else i = 1048576;
+
+ i = read(fd,x,i);
+ if (i < 1) {
+ sleep(1);
+ continue;
+ }
+
+ x += i;
+ xlen -= i;
+ }
+}
+
+
+static const u8
+ _0[16],
+ _9[32] = {9};
+static const gf
+ gf0,
+ gf1 = {1},
+ _121665 = {0xDB41,1},
+ D = {0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203},
+ D2 = {0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406},
+ X = {0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169},
+ Y = {0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666},
+ I = {0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83};
+
+static u32 L32(u32 x,int c) { return (x << c) | ((x&0xffffffff) >> (32 - c)); }
+
+static u32 ld32(const u8 *x)
+{
+ u32 u = x[3];
+ u = (u<<8)|x[2];
+ u = (u<<8)|x[1];
+ return (u<<8)|x[0];
+}
+
+static u64 dl64(const u8 *x)
+{
+ u64 i,u=0;
+ FOR(i,8) u=(u<<8)|x[i];
+ return u;
+}
+
+sv st32(u8 *x,u32 u)
+{
+ int i;
+ FOR(i,4) { x[i] = u; u >>= 8; }
+}
+
+sv ts64(u8 *x,u64 u)
+{
+ int i;
+ for (i = 7;i >= 0;--i) { x[i] = u; u >>= 8; }
+}
+
+static int vn(const u8 *x,const u8 *y,int n)
+{
+ int i = 0;
+ u32 d = 0;
+ FOR(i,n) d |= x[i]^y[i];
+ return (1 & ((d - 1) >> 8)) - 1;
+}
+
+int crypto_verify_16(const u8 *x,const u8 *y)
+{
+ return vn(x,y,16);
+}
+
+int crypto_verify_32(const u8 *x,const u8 *y)
+{
+ return vn(x,y,32);
+}
+
+sv core(u8 *out,const u8 *in,const u8 *k,const u8 *c,int h)
+{
+ u32 w[16],x[16],y[16],t[4];
+ int i,j,m;
+
+ FOR(i,4) {
+ x[5*i] = ld32(c+4*i);
+ x[1+i] = ld32(k+4*i);
+ x[6+i] = ld32(in+4*i);
+ x[11+i] = ld32(k+16+4*i);
+ }
+
+ FOR(i,16) y[i] = x[i];
+
+ FOR(i,20) {
+ FOR(j,4) {
+ FOR(m,4) t[m] = x[(5*j+4*m)%16];
+ t[1] ^= L32(t[0]+t[3], 7);
+ t[2] ^= L32(t[1]+t[0], 9);
+ t[3] ^= L32(t[2]+t[1],13);
+ t[0] ^= L32(t[3]+t[2],18);
+ FOR(m,4) w[4*j+(j+m)%4] = t[m];
+ }
+ FOR(m,16) x[m] = w[m];
+ }
+
+ if (h) {
+ FOR(i,16) x[i] += y[i];
+ FOR(i,4) {
+ x[5*i] -= ld32(c+4*i);
+ x[6+i] -= ld32(in+4*i);
+ }
+ FOR(i,4) {
+ st32(out+4*i,x[5*i]);
+ st32(out+16+4*i,x[6+i]);
+ }
+ } else
+ FOR(i,16) st32(out + 4 * i,x[i] + y[i]);
+}
+
+int crypto_core_salsa20(u8 *out,const u8 *in,const u8 *k,const u8 *c)
+{
+ core(out,in,k,c,0);
+ return 0;
+}
+
+int crypto_core_hsalsa20(u8 *out,const u8 *in,const u8 *k,const u8 *c)
+{
+ core(out,in,k,c,1);
+ return 0;
+}
+
+static const u8 sigma[16] = "expand 32-byte k";
+
+int crypto_stream_salsa20_xor(u8 *c,const u8 *m,u64 b,const u8 *n,const u8 *k)
+{
+ u8 z[16],x[64];
+ u32 u,i;
+ if (!b) return 0;
+ FOR(i,16) z[i] = 0;
+ FOR(i,8) z[i] = n[i];
+ while (b >= 64) {
+ crypto_core_salsa20(x,z,k,sigma);
+ FOR(i,64) c[i] = (m?m[i]:0) ^ x[i];
+ u = 1;
+ for (i = 8;i < 16;++i) {
+ u += (u32) z[i];
+ z[i] = u;
+ u >>= 8;
+ }
+ b -= 64;
+ c += 64;
+ if (m) m += 64;
+ }
+ if (b) {
+ crypto_core_salsa20(x,z,k,sigma);
+ FOR(i,b) c[i] = (m?m[i]:0) ^ x[i];
+ }
+ return 0;
+}
+
+int crypto_stream_salsa20(u8 *c,u64 d,const u8 *n,const u8 *k)
+{
+ return crypto_stream_salsa20_xor(c,0,d,n,k);
+}
+
+int crypto_stream(u8 *c,u64 d,const u8 *n,const u8 *k)
+{
+ u8 s[32];
+ crypto_core_hsalsa20(s,n,k,sigma);
+ return crypto_stream_salsa20(c,d,n+16,s);
+}
+
+int crypto_stream_xor(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k)
+{
+ u8 s[32];
+ crypto_core_hsalsa20(s,n,k,sigma);
+ return crypto_stream_salsa20_xor(c,m,d,n+16,s);
+}
+
+sv add1305(u32 *h,const u32 *c)
+{
+ u32 j,u = 0;
+ FOR(j,17) {
+ u += h[j] + c[j];
+ h[j] = u & 255;
+ u >>= 8;
+ }
+}
+
+static const u32 minusp[17] = {
+ 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252
+} ;
+
+int crypto_onetimeauth(u8 *out,const u8 *m,u64 n,const u8 *k)
+{
+ u32 s,i,j,u,x[17],r[17],h[17],c[17],g[17];
+
+ FOR(j,17) r[j]=h[j]=0;
+ FOR(j,16) r[j]=k[j];
+ r[3]&=15;
+ r[4]&=252;
+ r[7]&=15;
+ r[8]&=252;
+ r[11]&=15;
+ r[12]&=252;
+ r[15]&=15;
+
+ while (n > 0) {
+ FOR(j,17) c[j] = 0;
+ for (j = 0;(j < 16) && (j < n);++j) c[j] = m[j];
+ c[j] = 1;
+ m += j; n -= j;
+ add1305(h,c);
+ FOR(i,17) {
+ x[i] = 0;
+ FOR(j,17) x[i] += h[j] * ((j <= i) ? r[i - j] : 320 * r[i + 17 - j]);
+ }
+ FOR(i,17) h[i] = x[i];
+ u = 0;
+ FOR(j,16) {
+ u += h[j];
+ h[j] = u & 255;
+ u >>= 8;
+ }
+ u += h[16]; h[16] = u & 3;
+ u = 5 * (u >> 2);
+ FOR(j,16) {
+ u += h[j];
+ h[j] = u & 255;
+ u >>= 8;
+ }
+ u += h[16]; h[16] = u;
+ }
+
+ FOR(j,17) g[j] = h[j];
+ add1305(h,minusp);
+ s = -(h[16] >> 7);
+ FOR(j,17) h[j] ^= s & (g[j] ^ h[j]);
+
+ FOR(j,16) c[j] = k[j + 16];
+ c[16] = 0;
+ add1305(h,c);
+ FOR(j,16) out[j] = h[j];
+ return 0;
+}
+
+int crypto_onetimeauth_verify(const u8 *h,const u8 *m,u64 n,const u8 *k)
+{
+ u8 x[16];
+ crypto_onetimeauth(x,m,n,k);
+ return crypto_verify_16(h,x);
+}
+
+int crypto_secretbox(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k)
+{
+ int i;
+ if (d < 32) return -1;
+ crypto_stream_xor(c,m,d,n,k);
+ crypto_onetimeauth(c + 16,c + 32,d - 32,c);
+ FOR(i,16) c[i] = 0;
+ return 0;
+}
+
+int crypto_secretbox_open(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *k)
+{
+ int i;
+ u8 x[32];
+ if (d < 32) return -1;
+ crypto_stream(x,32,n,k);
+ if (crypto_onetimeauth_verify(c + 16,c + 32,d - 32,x) != 0) return -1;
+ crypto_stream_xor(m,c,d,n,k);
+ FOR(i,32) m[i] = 0;
+ return 0;
+}
+
+sv set25519(gf r, const gf a)
+{
+ int i;
+ FOR(i,16) r[i]=a[i];
+}
+
+sv car25519(gf o)
+{
+ int i;
+ i64 c;
+ FOR(i,16) {
+ o[i]+=(1LL<<16);
+ c=o[i]>>16;
+ o[(i+1)*(i<15)]+=c-1+37*(c-1)*(i==15);
+ o[i]-=c<<16;
+ }
+}
+
+sv sel25519(gf p,gf q,int b)
+{
+ i64 t,i,c=~(b-1);
+ FOR(i,16) {
+ t= c&(p[i]^q[i]);
+ p[i]^=t;
+ q[i]^=t;
+ }
+}
+
+sv pack25519(u8 *o,const gf n)
+{
+ int i,j,b;
+ gf m,t;
+ FOR(i,16) t[i]=n[i];
+ car25519(t);
+ car25519(t);
+ car25519(t);
+ FOR(j,2) {
+ m[0]=t[0]-0xffed;
+ for(i=1;i<15;i++) {
+ m[i]=t[i]-0xffff-((m[i-1]>>16)&1);
+ m[i-1]&=0xffff;
+ }
+ m[15]=t[15]-0x7fff-((m[14]>>16)&1);
+ b=(m[15]>>16)&1;
+ m[14]&=0xffff;
+ sel25519(t,m,1-b);
+ }
+ FOR(i,16) {
+ o[2*i]=t[i]&0xff;
+ o[2*i+1]=t[i]>>8;
+ }
+}
+
+static int neq25519(const gf a, const gf b)
+{
+ u8 c[32],d[32];
+ pack25519(c,a);
+ pack25519(d,b);
+ return crypto_verify_32(c,d);
+}
+
+static u8 par25519(const gf a)
+{
+ u8 d[32];
+ pack25519(d,a);
+ return d[0]&1;
+}
+
+sv unpack25519(gf o, const u8 *n)
+{
+ int i;
+ FOR(i,16) o[i]=n[2*i]+((i64)n[2*i+1]<<8);
+ o[15]&=0x7fff;
+}
+
+sv A(gf o,const gf a,const gf b)
+{
+ int i;
+ FOR(i,16) o[i]=a[i]+b[i];
+}
+
+sv Z(gf o,const gf a,const gf b)
+{
+ int i;
+ FOR(i,16) o[i]=a[i]-b[i];
+}
+
+sv M(gf o,const gf a,const gf b)
+{
+ i64 i,j,t[31];
+ FOR(i,31) t[i]=0;
+ FOR(i,16) FOR(j,16) t[i+j]+=a[i]*b[j];
+ FOR(i,15) t[i]+=38*t[i+16];
+ FOR(i,16) o[i]=t[i];
+ car25519(o);
+ car25519(o);
+}
+
+sv S(gf o,const gf a)
+{
+ M(o,a,a);
+}
+
+sv inv25519(gf o,const gf i)
+{
+ gf c;
+ int a;
+ FOR(a,16) c[a]=i[a];
+ for(a=253;a>=0;a--) {
+ S(c,c);
+ if(a!=2&&a!=4) M(c,c,i);
+ }
+ FOR(a,16) o[a]=c[a];
+}
+
+sv pow2523(gf o,const gf i)
+{
+ gf c;
+ int a;
+ FOR(a,16) c[a]=i[a];
+ for(a=250;a>=0;a--) {
+ S(c,c);
+ if(a!=1) M(c,c,i);
+ }
+ FOR(a,16) o[a]=c[a];
+}
+
+int crypto_scalarmult(u8 *q,const u8 *n,const u8 *p)
+{
+ u8 z[32];
+ i64 x[80],r,i;
+ gf a,b,c,d,e,f;
+ FOR(i,31) z[i]=n[i];
+ z[31]=(n[31]&127)|64;
+ z[0]&=248;
+ unpack25519(x,p);
+ FOR(i,16) {
+ b[i]=x[i];
+ d[i]=a[i]=c[i]=0;
+ }
+ a[0]=d[0]=1;
+ for(i=254;i>=0;--i) {
+ r=(z[i>>3]>>(i&7))&1;
+ sel25519(a,b,r);
+ sel25519(c,d,r);
+ A(e,a,c);
+ Z(a,a,c);
+ A(c,b,d);
+ Z(b,b,d);
+ S(d,e);
+ S(f,a);
+ M(a,c,a);
+ M(c,b,e);
+ A(e,a,c);
+ Z(a,a,c);
+ S(b,a);
+ Z(c,d,f);
+ M(a,c,_121665);
+ A(a,a,d);
+ M(c,c,a);
+ M(a,d,f);
+ M(d,b,x);
+ S(b,e);
+ sel25519(a,b,r);
+ sel25519(c,d,r);
+ }
+ FOR(i,16) {
+ x[i+16]=a[i];
+ x[i+32]=c[i];
+ x[i+48]=b[i];
+ x[i+64]=d[i];
+ }
+ inv25519(x+32,x+32);
+ M(x+16,x+16,x+32);
+ pack25519(q,x+16);
+ return 0;
+}
+
+int crypto_scalarmult_base(u8 *q,const u8 *n)
+{
+ return crypto_scalarmult(q,n,_9);
+}
+
+int crypto_box_keypair(u8 *y,u8 *x)
+{
+ randombytes(x,32);
+ return crypto_scalarmult_base(y,x);
+}
+
+int crypto_box_beforenm(u8 *k,const u8 *y,const u8 *x)
+{
+ u8 s[32];
+ crypto_scalarmult(s,x,y);
+ return crypto_core_hsalsa20(k,_0,s,sigma);
+}
+
+int crypto_box_afternm(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k)
+{
+ return crypto_secretbox(c,m,d,n,k);
+}
+
+int crypto_box_open_afternm(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *k)
+{
+ return crypto_secretbox_open(m,c,d,n,k);
+}
+
+int crypto_box(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *y,const u8 *x)
+{
+ u8 k[32];
+ crypto_box_beforenm(k,y,x);
+ return crypto_box_afternm(c,m,d,n,k);
+}
+
+int crypto_box_open(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *y,const u8 *x)
+{
+ u8 k[32];
+ crypto_box_beforenm(k,y,x);
+ return crypto_box_open_afternm(m,c,d,n,k);
+}
+
+static u64 R(u64 x,int c) { return (x >> c) | (x << (64 - c)); }
+static u64 Ch(u64 x,u64 y,u64 z) { return (x & y) ^ (~x & z); }
+static u64 Maj(u64 x,u64 y,u64 z) { return (x & y) ^ (x & z) ^ (y & z); }
+static u64 Sigma0(u64 x) { return R(x,28) ^ R(x,34) ^ R(x,39); }
+static u64 Sigma1(u64 x) { return R(x,14) ^ R(x,18) ^ R(x,41); }
+static u64 sigma0(u64 x) { return R(x, 1) ^ R(x, 8) ^ (x >> 7); }
+static u64 sigma1(u64 x) { return R(x,19) ^ R(x,61) ^ (x >> 6); }
+
+static const u64 K[80] =
+{
+ 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,
+ 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
+ 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+ 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
+ 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,
+ 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+ 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,
+ 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
+ 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+ 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
+ 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,
+ 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+ 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,
+ 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
+ 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+ 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
+ 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,
+ 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+ 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,
+ 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL
+};
+
+int crypto_hashblocks(u8 *x,const u8 *m,u64 n)
+{
+ u64 z[8],b[8],a[8],w[16],t;
+ int i,j;
+
+ FOR(i,8) z[i] = a[i] = dl64(x + 8 * i);
+
+ while (n >= 128) {
+ FOR(i,16) w[i] = dl64(m + 8 * i);
+
+ FOR(i,80) {
+ FOR(j,8) b[j] = a[j];
+ t = a[7] + Sigma1(a[4]) + Ch(a[4],a[5],a[6]) + K[i] + w[i%16];
+ b[7] = t + Sigma0(a[0]) + Maj(a[0],a[1],a[2]);
+ b[3] += t;
+ FOR(j,8) a[(j+1)%8] = b[j];
+ if (i%16 == 15)
+ FOR(j,16)
+ w[j] += w[(j+9)%16] + sigma0(w[(j+1)%16]) + sigma1(w[(j+14)%16]);
+ }
+
+ FOR(i,8) { a[i] += z[i]; z[i] = a[i]; }
+
+ m += 128;
+ n -= 128;
+ }
+
+ FOR(i,8) ts64(x+8*i,z[i]);
+
+ return n;
+}
+
+static const u8 iv[64] = {
+ 0x6a,0x09,0xe6,0x67,0xf3,0xbc,0xc9,0x08,
+ 0xbb,0x67,0xae,0x85,0x84,0xca,0xa7,0x3b,
+ 0x3c,0x6e,0xf3,0x72,0xfe,0x94,0xf8,0x2b,
+ 0xa5,0x4f,0xf5,0x3a,0x5f,0x1d,0x36,0xf1,
+ 0x51,0x0e,0x52,0x7f,0xad,0xe6,0x82,0xd1,
+ 0x9b,0x05,0x68,0x8c,0x2b,0x3e,0x6c,0x1f,
+ 0x1f,0x83,0xd9,0xab,0xfb,0x41,0xbd,0x6b,
+ 0x5b,0xe0,0xcd,0x19,0x13,0x7e,0x21,0x79
+} ;
+
+int crypto_hash(u8 *out,const u8 *m,u64 n)
+{
+ u8 h[64],x[256];
+ u64 i,b = n;
+
+ FOR(i,64) h[i] = iv[i];
+
+ crypto_hashblocks(h,m,n);
+ m += n;
+ n &= 127;
+ m -= n;
+
+ FOR(i,256) x[i] = 0;
+ FOR(i,n) x[i] = m[i];
+ x[n] = 128;
+
+ n = 256-128*(n<112);
+ x[n-9] = b >> 61;
+ ts64(x+n-8,b<<3);
+ crypto_hashblocks(h,x,n);
+
+ FOR(i,64) out[i] = h[i];
+
+ return 0;
+}
+
+sv add(gf p[4],gf q[4])
+{
+ gf a,b,c,d,t,e,f,g,h;
+
+ Z(a, p[1], p[0]);
+ Z(t, q[1], q[0]);
+ M(a, a, t);
+ A(b, p[0], p[1]);
+ A(t, q[0], q[1]);
+ M(b, b, t);
+ M(c, p[3], q[3]);
+ M(c, c, D2);
+ M(d, p[2], q[2]);
+ A(d, d, d);
+ Z(e, b, a);
+ Z(f, d, c);
+ A(g, d, c);
+ A(h, b, a);
+
+ M(p[0], e, f);
+ M(p[1], h, g);
+ M(p[2], g, f);
+ M(p[3], e, h);
+}
+
+sv cswap(gf p[4],gf q[4],u8 b)
+{
+ int i;
+ FOR(i,4)
+ sel25519(p[i],q[i],b);
+}
+
+sv pack(u8 *r,gf p[4])
+{
+ gf tx, ty, zi;
+ inv25519(zi, p[2]);
+ M(tx, p[0], zi);
+ M(ty, p[1], zi);
+ pack25519(r, ty);
+ r[31] ^= par25519(tx) << 7;
+}
+
+sv scalarmult(gf p[4],gf q[4],const u8 *s)
+{
+ int i;
+ set25519(p[0],gf0);
+ set25519(p[1],gf1);
+ set25519(p[2],gf1);
+ set25519(p[3],gf0);
+ for (i = 255;i >= 0;--i) {
+ u8 b = (s[i/8]>>(i&7))&1;
+ cswap(p,q,b);
+ add(q,p);
+ add(p,p);
+ cswap(p,q,b);
+ }
+}
+
+sv scalarbase(gf p[4],const u8 *s)
+{
+ gf q[4];
+ set25519(q[0],X);
+ set25519(q[1],Y);
+ set25519(q[2],gf1);
+ M(q[3],X,Y);
+ scalarmult(p,q,s);
+}
+
+int crypto_sign_keypair(u8 *pk, u8 *sk)
+{
+ u8 d[64];
+ gf p[4];
+ int i;
+
+ randombytes(sk, 32);
+ crypto_hash(d, sk, 32);
+ d[0] &= 248;
+ d[31] &= 127;
+ d[31] |= 64;
+
+ scalarbase(p,d);
+ pack(pk,p);
+
+ FOR(i,32) sk[32 + i] = pk[i];
+ return 0;
+}
+
+static const u64 L[32] = {0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10};
+
+sv modL(u8 *r,i64 x[64])
+{
+ i64 carry,i,j;
+ for (i = 63;i >= 32;--i) {
+ carry = 0;
+ for (j = i - 32;j < i - 12;++j) {
+ x[j] += carry - 16 * x[i] * L[j - (i - 32)];
+ carry = (x[j] + 128) >> 8;
+ x[j] -= carry << 8;
+ }
+ x[j] += carry;
+ x[i] = 0;
+ }
+ carry = 0;
+ FOR(j,32) {
+ x[j] += carry - (x[31] >> 4) * L[j];
+ carry = x[j] >> 8;
+ x[j] &= 255;
+ }
+ FOR(j,32) x[j] -= carry * L[j];
+ FOR(i,32) {
+ x[i+1] += x[i] >> 8;
+ r[i] = x[i] & 255;
+ }
+}
+
+sv reduce(u8 *r)
+{
+ i64 x[64],i;
+ FOR(i,64) x[i] = (u64) r[i];
+ FOR(i,64) r[i] = 0;
+ modL(r,x);
+}
+
+int crypto_sign(u8 *sm,u64 *smlen,const u8 *m,u64 n,const u8 *sk)
+{
+ u8 d[64],h[64],r[64];
+ u64 i;
+ i64 j,x[64];
+ gf p[4];
+
+ crypto_hash(d, sk, 32);
+ d[0] &= 248;
+ d[31] &= 127;
+ d[31] |= 64;
+
+ *smlen = n+64;
+ FOR(i,n) sm[64 + i] = m[i];
+ FOR(i,32) sm[32 + i] = d[32 + i];
+
+ crypto_hash(r, sm+32, n+32);
+ reduce(r);
+ scalarbase(p,r);
+ pack(sm,p);
+
+ FOR(i,32) sm[i+32] = sk[i+32];
+ crypto_hash(h,sm,n + 64);
+ reduce(h);
+
+ FOR(i,64) x[i] = 0;
+ FOR(i,32) x[i] = (u64) r[i];
+ FOR(i,32) FOR(j,32) x[i+j] += h[i] * (u64) d[j];
+ modL(sm + 32,x);
+
+ return 0;
+}
+
+static int unpackneg(gf r[4],const u8 p[32])
+{
+ gf t, chk, num, den, den2, den4, den6;
+ set25519(r[2],gf1);
+ unpack25519(r[1],p);
+ S(num,r[1]);
+ M(den,num,D);
+ Z(num,num,r[2]);
+ A(den,r[2],den);
+
+ S(den2,den);
+ S(den4,den2);
+ M(den6,den4,den2);
+ M(t,den6,num);
+ M(t,t,den);
+
+ pow2523(t,t);
+ M(t,t,num);
+ M(t,t,den);
+ M(t,t,den);
+ M(r[0],t,den);
+
+ S(chk,r[0]);
+ M(chk,chk,den);
+ if (neq25519(chk, num)) M(r[0],r[0],I);
+
+ S(chk,r[0]);
+ M(chk,chk,den);
+ if (neq25519(chk, num)) return -1;
+
+ if (par25519(r[0]) == (p[31]>>7)) Z(r[0],gf0,r[0]);
+
+ M(r[3],r[0],r[1]);
+ return 0;
+}
+
+int crypto_sign_open(u8 *m,u64 *mlen,const u8 *sm,u64 n,const u8 *pk)
+{
+ u64 i;
+ u8 t[32],h[64];
+ gf p[4],q[4];
+
+ *mlen = -1;
+ if (n < 64) return -1;
+
+ if (unpackneg(q,pk)) return -1;
+
+ FOR(i,n) m[i] = sm[i];
+ FOR(i,32) m[i+32] = pk[i];
+ crypto_hash(h,m,n);
+ reduce(h);
+ scalarmult(p,q,h);
+
+ scalarbase(q,sm + 32);
+ add(p,q);
+ pack(t,p);
+
+ n -= 64;
+ if (crypto_verify_32(sm, t)) {
+ FOR(i,n) m[i] = 0;
+ return -1;
+ }
+
+ FOR(i,n) m[i] = sm[i + 64];
+ *mlen = n;
+ return 0;
+}
diff --git a/src/tweetnacl.h b/src/tweetnacl.h
new file mode 100644
index 00000000..508876d8
--- /dev/null
+++ b/src/tweetnacl.h
@@ -0,0 +1,277 @@
+#include
+#include
+#include
+#include
+
+#ifndef TWEETNACL_H
+#define TWEETNACL_H
+#define crypto_auth_PRIMITIVE "hmacsha512256"
+#define crypto_auth crypto_auth_hmacsha512256
+#define crypto_auth_verify crypto_auth_hmacsha512256_verify
+#define crypto_auth_BYTES crypto_auth_hmacsha512256_BYTES
+#define crypto_auth_KEYBYTES crypto_auth_hmacsha512256_KEYBYTES
+#define crypto_auth_IMPLEMENTATION crypto_auth_hmacsha512256_IMPLEMENTATION
+#define crypto_auth_VERSION crypto_auth_hmacsha512256_VERSION
+#define crypto_auth_hmacsha512256_tweet_BYTES 32
+#define crypto_auth_hmacsha512256_tweet_KEYBYTES 32
+extern int crypto_auth_hmacsha512256_tweet(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *);
+extern int crypto_auth_hmacsha512256_tweet_verify(const unsigned char *,const unsigned char *,unsigned long long,const unsigned char *);
+#define crypto_auth_hmacsha512256_tweet_VERSION "-"
+#define crypto_auth_hmacsha512256 crypto_auth_hmacsha512256_tweet
+#define crypto_auth_hmacsha512256_verify crypto_auth_hmacsha512256_tweet_verify
+#define crypto_auth_hmacsha512256_BYTES crypto_auth_hmacsha512256_tweet_BYTES
+#define crypto_auth_hmacsha512256_KEYBYTES crypto_auth_hmacsha512256_tweet_KEYBYTES
+#define crypto_auth_hmacsha512256_VERSION crypto_auth_hmacsha512256_tweet_VERSION
+#define crypto_auth_hmacsha512256_IMPLEMENTATION "crypto_auth/hmacsha512256/tweet"
+#define crypto_box_PRIMITIVE "curve25519xsalsa20poly1305"
+#define crypto_box crypto_box_curve25519xsalsa20poly1305
+#define crypto_box_open crypto_box_curve25519xsalsa20poly1305_open
+#define crypto_box_keypair crypto_box_curve25519xsalsa20poly1305_keypair
+#define crypto_box_beforenm crypto_box_curve25519xsalsa20poly1305_beforenm
+#define crypto_box_afternm crypto_box_curve25519xsalsa20poly1305_afternm
+#define crypto_box_open_afternm crypto_box_curve25519xsalsa20poly1305_open_afternm
+#define crypto_box_PUBLICKEYBYTES crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES
+#define crypto_box_SECRETKEYBYTES crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES
+#define crypto_box_BEFORENMBYTES crypto_box_curve25519xsalsa20poly1305_BEFORENMBYTES
+#define crypto_box_NONCEBYTES crypto_box_curve25519xsalsa20poly1305_NONCEBYTES
+#define crypto_box_ZEROBYTES crypto_box_curve25519xsalsa20poly1305_ZEROBYTES
+#define crypto_box_BOXZEROBYTES crypto_box_curve25519xsalsa20poly1305_BOXZEROBYTES
+#define crypto_box_IMPLEMENTATION crypto_box_curve25519xsalsa20poly1305_IMPLEMENTATION
+#define crypto_box_VERSION crypto_box_curve25519xsalsa20poly1305_VERSION
+#define crypto_box_curve25519xsalsa20poly1305_tweet_PUBLICKEYBYTES 32
+#define crypto_box_curve25519xsalsa20poly1305_tweet_SECRETKEYBYTES 32
+#define crypto_box_curve25519xsalsa20poly1305_tweet_BEFORENMBYTES 32
+#define crypto_box_curve25519xsalsa20poly1305_tweet_NONCEBYTES 24
+#define crypto_box_curve25519xsalsa20poly1305_tweet_ZEROBYTES 32
+#define crypto_box_curve25519xsalsa20poly1305_tweet_BOXZEROBYTES 16
+extern int crypto_box_curve25519xsalsa20poly1305_tweet(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *,const unsigned char *);
+extern int crypto_box_curve25519xsalsa20poly1305_tweet_open(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *,const unsigned char *);
+extern int crypto_box_curve25519xsalsa20poly1305_tweet_keypair(unsigned char *,unsigned char *);
+extern int crypto_box_curve25519xsalsa20poly1305_tweet_beforenm(unsigned char *,const unsigned char *,const unsigned char *);
+extern int crypto_box_curve25519xsalsa20poly1305_tweet_afternm(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *);
+extern int crypto_box_curve25519xsalsa20poly1305_tweet_open_afternm(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *);
+#define crypto_box_curve25519xsalsa20poly1305_tweet_VERSION "-"
+#define crypto_box_curve25519xsalsa20poly1305 crypto_box_curve25519xsalsa20poly1305_tweet
+#define crypto_box_curve25519xsalsa20poly1305_open crypto_box_curve25519xsalsa20poly1305_tweet_open
+#define crypto_box_curve25519xsalsa20poly1305_keypair crypto_box_curve25519xsalsa20poly1305_tweet_keypair
+#define crypto_box_curve25519xsalsa20poly1305_beforenm crypto_box_curve25519xsalsa20poly1305_tweet_beforenm
+#define crypto_box_curve25519xsalsa20poly1305_afternm crypto_box_curve25519xsalsa20poly1305_tweet_afternm
+#define crypto_box_curve25519xsalsa20poly1305_open_afternm crypto_box_curve25519xsalsa20poly1305_tweet_open_afternm
+#define crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES crypto_box_curve25519xsalsa20poly1305_tweet_PUBLICKEYBYTES
+#define crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES crypto_box_curve25519xsalsa20poly1305_tweet_SECRETKEYBYTES
+#define crypto_box_curve25519xsalsa20poly1305_BEFORENMBYTES crypto_box_curve25519xsalsa20poly1305_tweet_BEFORENMBYTES
+#define crypto_box_curve25519xsalsa20poly1305_NONCEBYTES crypto_box_curve25519xsalsa20poly1305_tweet_NONCEBYTES
+#define crypto_box_curve25519xsalsa20poly1305_ZEROBYTES crypto_box_curve25519xsalsa20poly1305_tweet_ZEROBYTES
+#define crypto_box_curve25519xsalsa20poly1305_BOXZEROBYTES crypto_box_curve25519xsalsa20poly1305_tweet_BOXZEROBYTES
+#define crypto_box_curve25519xsalsa20poly1305_VERSION crypto_box_curve25519xsalsa20poly1305_tweet_VERSION
+#define crypto_box_curve25519xsalsa20poly1305_IMPLEMENTATION "crypto_box/curve25519xsalsa20poly1305/tweet"
+#define crypto_core_PRIMITIVE "salsa20"
+#define crypto_core crypto_core_salsa20
+#define crypto_core_OUTPUTBYTES crypto_core_salsa20_OUTPUTBYTES
+#define crypto_core_INPUTBYTES crypto_core_salsa20_INPUTBYTES
+#define crypto_core_KEYBYTES crypto_core_salsa20_KEYBYTES
+#define crypto_core_CONSTBYTES crypto_core_salsa20_CONSTBYTES
+#define crypto_core_IMPLEMENTATION crypto_core_salsa20_IMPLEMENTATION
+#define crypto_core_VERSION crypto_core_salsa20_VERSION
+#define crypto_core_salsa20_tweet_OUTPUTBYTES 64
+#define crypto_core_salsa20_tweet_INPUTBYTES 16
+#define crypto_core_salsa20_tweet_KEYBYTES 32
+#define crypto_core_salsa20_tweet_CONSTBYTES 16
+extern int crypto_core_salsa20_tweet(unsigned char *,const unsigned char *,const unsigned char *,const unsigned char *);
+#define crypto_core_salsa20_tweet_VERSION "-"
+#define crypto_core_salsa20 crypto_core_salsa20_tweet
+#define crypto_core_salsa20_OUTPUTBYTES crypto_core_salsa20_tweet_OUTPUTBYTES
+#define crypto_core_salsa20_INPUTBYTES crypto_core_salsa20_tweet_INPUTBYTES
+#define crypto_core_salsa20_KEYBYTES crypto_core_salsa20_tweet_KEYBYTES
+#define crypto_core_salsa20_CONSTBYTES crypto_core_salsa20_tweet_CONSTBYTES
+#define crypto_core_salsa20_VERSION crypto_core_salsa20_tweet_VERSION
+#define crypto_core_salsa20_IMPLEMENTATION "crypto_core/salsa20/tweet"
+#define crypto_core_hsalsa20_tweet_OUTPUTBYTES 32
+#define crypto_core_hsalsa20_tweet_INPUTBYTES 16
+#define crypto_core_hsalsa20_tweet_KEYBYTES 32
+#define crypto_core_hsalsa20_tweet_CONSTBYTES 16
+extern int crypto_core_hsalsa20_tweet(unsigned char *,const unsigned char *,const unsigned char *,const unsigned char *);
+#define crypto_core_hsalsa20_tweet_VERSION "-"
+#define crypto_core_hsalsa20 crypto_core_hsalsa20_tweet
+#define crypto_core_hsalsa20_OUTPUTBYTES crypto_core_hsalsa20_tweet_OUTPUTBYTES
+#define crypto_core_hsalsa20_INPUTBYTES crypto_core_hsalsa20_tweet_INPUTBYTES
+#define crypto_core_hsalsa20_KEYBYTES crypto_core_hsalsa20_tweet_KEYBYTES
+#define crypto_core_hsalsa20_CONSTBYTES crypto_core_hsalsa20_tweet_CONSTBYTES
+#define crypto_core_hsalsa20_VERSION crypto_core_hsalsa20_tweet_VERSION
+#define crypto_core_hsalsa20_IMPLEMENTATION "crypto_core/hsalsa20/tweet"
+#define crypto_hashblocks_PRIMITIVE "sha512"
+#define crypto_hashblocks crypto_hashblocks_sha512
+#define crypto_hashblocks_STATEBYTES crypto_hashblocks_sha512_STATEBYTES
+#define crypto_hashblocks_BLOCKBYTES crypto_hashblocks_sha512_BLOCKBYTES
+#define crypto_hashblocks_IMPLEMENTATION crypto_hashblocks_sha512_IMPLEMENTATION
+#define crypto_hashblocks_VERSION crypto_hashblocks_sha512_VERSION
+#define crypto_hashblocks_sha512_tweet_STATEBYTES 64
+#define crypto_hashblocks_sha512_tweet_BLOCKBYTES 128
+extern int crypto_hashblocks_sha512_tweet(unsigned char *,const unsigned char *,unsigned long long);
+#define crypto_hashblocks_sha512_tweet_VERSION "-"
+#define crypto_hashblocks_sha512 crypto_hashblocks_sha512_tweet
+#define crypto_hashblocks_sha512_STATEBYTES crypto_hashblocks_sha512_tweet_STATEBYTES
+#define crypto_hashblocks_sha512_BLOCKBYTES crypto_hashblocks_sha512_tweet_BLOCKBYTES
+#define crypto_hashblocks_sha512_VERSION crypto_hashblocks_sha512_tweet_VERSION
+#define crypto_hashblocks_sha512_IMPLEMENTATION "crypto_hashblocks/sha512/tweet"
+#define crypto_hashblocks_sha256_tweet_STATEBYTES 32
+#define crypto_hashblocks_sha256_tweet_BLOCKBYTES 64
+extern int crypto_hashblocks_sha256_tweet(unsigned char *,const unsigned char *,unsigned long long);
+#define crypto_hashblocks_sha256_tweet_VERSION "-"
+#define crypto_hashblocks_sha256 crypto_hashblocks_sha256_tweet
+#define crypto_hashblocks_sha256_STATEBYTES crypto_hashblocks_sha256_tweet_STATEBYTES
+#define crypto_hashblocks_sha256_BLOCKBYTES crypto_hashblocks_sha256_tweet_BLOCKBYTES
+#define crypto_hashblocks_sha256_VERSION crypto_hashblocks_sha256_tweet_VERSION
+#define crypto_hashblocks_sha256_IMPLEMENTATION "crypto_hashblocks/sha256/tweet"
+#define crypto_hash_PRIMITIVE "sha512"
+#define crypto_hash crypto_hash_sha512
+#define crypto_hash_BYTES crypto_hash_sha512_BYTES
+#define crypto_hash_IMPLEMENTATION crypto_hash_sha512_IMPLEMENTATION
+#define crypto_hash_VERSION crypto_hash_sha512_VERSION
+#define crypto_hash_sha512_tweet_BYTES 64
+extern int crypto_hash_sha512_tweet(unsigned char *,const unsigned char *,unsigned long long);
+#define crypto_hash_sha512_tweet_VERSION "-"
+#define crypto_hash_sha512 crypto_hash_sha512_tweet
+#define crypto_hash_sha512_BYTES crypto_hash_sha512_tweet_BYTES
+#define crypto_hash_sha512_VERSION crypto_hash_sha512_tweet_VERSION
+#define crypto_hash_sha512_IMPLEMENTATION "crypto_hash/sha512/tweet"
+#define crypto_hash_sha256_tweet_BYTES 32
+extern int crypto_hash_sha256_tweet(unsigned char *,const unsigned char *,unsigned long long);
+#define crypto_hash_sha256_tweet_VERSION "-"
+#define crypto_hash_sha256 crypto_hash_sha256_tweet
+#define crypto_hash_sha256_BYTES crypto_hash_sha256_tweet_BYTES
+#define crypto_hash_sha256_VERSION crypto_hash_sha256_tweet_VERSION
+#define crypto_hash_sha256_IMPLEMENTATION "crypto_hash/sha256/tweet"
+#define crypto_onetimeauth_PRIMITIVE "poly1305"
+#define crypto_onetimeauth crypto_onetimeauth_poly1305
+#define crypto_onetimeauth_verify crypto_onetimeauth_poly1305_verify
+#define crypto_onetimeauth_BYTES crypto_onetimeauth_poly1305_BYTES
+#define crypto_onetimeauth_KEYBYTES crypto_onetimeauth_poly1305_KEYBYTES
+#define crypto_onetimeauth_IMPLEMENTATION crypto_onetimeauth_poly1305_IMPLEMENTATION
+#define crypto_onetimeauth_VERSION crypto_onetimeauth_poly1305_VERSION
+#define crypto_onetimeauth_poly1305_tweet_BYTES 16
+#define crypto_onetimeauth_poly1305_tweet_KEYBYTES 32
+extern int crypto_onetimeauth_poly1305_tweet(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *);
+extern int crypto_onetimeauth_poly1305_tweet_verify(const unsigned char *,const unsigned char *,unsigned long long,const unsigned char *);
+#define crypto_onetimeauth_poly1305_tweet_VERSION "-"
+#define crypto_onetimeauth_poly1305 crypto_onetimeauth_poly1305_tweet
+#define crypto_onetimeauth_poly1305_verify crypto_onetimeauth_poly1305_tweet_verify
+#define crypto_onetimeauth_poly1305_BYTES crypto_onetimeauth_poly1305_tweet_BYTES
+#define crypto_onetimeauth_poly1305_KEYBYTES crypto_onetimeauth_poly1305_tweet_KEYBYTES
+#define crypto_onetimeauth_poly1305_VERSION crypto_onetimeauth_poly1305_tweet_VERSION
+#define crypto_onetimeauth_poly1305_IMPLEMENTATION "crypto_onetimeauth/poly1305/tweet"
+#define crypto_scalarmult_PRIMITIVE "curve25519"
+#define crypto_scalarmult crypto_scalarmult_curve25519
+#define crypto_scalarmult_base crypto_scalarmult_curve25519_base
+#define crypto_scalarmult_BYTES crypto_scalarmult_curve25519_BYTES
+#define crypto_scalarmult_SCALARBYTES crypto_scalarmult_curve25519_SCALARBYTES
+#define crypto_scalarmult_IMPLEMENTATION crypto_scalarmult_curve25519_IMPLEMENTATION
+#define crypto_scalarmult_VERSION crypto_scalarmult_curve25519_VERSION
+#define crypto_scalarmult_curve25519_tweet_BYTES 32
+#define crypto_scalarmult_curve25519_tweet_SCALARBYTES 32
+extern int crypto_scalarmult_curve25519_tweet(unsigned char *,const unsigned char *,const unsigned char *);
+extern int crypto_scalarmult_curve25519_tweet_base(unsigned char *,const unsigned char *);
+#define crypto_scalarmult_curve25519_tweet_VERSION "-"
+#define crypto_scalarmult_curve25519 crypto_scalarmult_curve25519_tweet
+#define crypto_scalarmult_curve25519_base crypto_scalarmult_curve25519_tweet_base
+#define crypto_scalarmult_curve25519_BYTES crypto_scalarmult_curve25519_tweet_BYTES
+#define crypto_scalarmult_curve25519_SCALARBYTES crypto_scalarmult_curve25519_tweet_SCALARBYTES
+#define crypto_scalarmult_curve25519_VERSION crypto_scalarmult_curve25519_tweet_VERSION
+#define crypto_scalarmult_curve25519_IMPLEMENTATION "crypto_scalarmult/curve25519/tweet"
+#define crypto_secretbox_PRIMITIVE "xsalsa20poly1305"
+#define crypto_secretbox crypto_secretbox_xsalsa20poly1305
+#define crypto_secretbox_open crypto_secretbox_xsalsa20poly1305_open
+#define crypto_secretbox_KEYBYTES crypto_secretbox_xsalsa20poly1305_KEYBYTES
+#define crypto_secretbox_NONCEBYTES crypto_secretbox_xsalsa20poly1305_NONCEBYTES
+#define crypto_secretbox_ZEROBYTES crypto_secretbox_xsalsa20poly1305_ZEROBYTES
+#define crypto_secretbox_BOXZEROBYTES crypto_secretbox_xsalsa20poly1305_BOXZEROBYTES
+#define crypto_secretbox_IMPLEMENTATION crypto_secretbox_xsalsa20poly1305_IMPLEMENTATION
+#define crypto_secretbox_VERSION crypto_secretbox_xsalsa20poly1305_VERSION
+#define crypto_secretbox_xsalsa20poly1305_tweet_KEYBYTES 32
+#define crypto_secretbox_xsalsa20poly1305_tweet_NONCEBYTES 24
+#define crypto_secretbox_xsalsa20poly1305_tweet_ZEROBYTES 32
+#define crypto_secretbox_xsalsa20poly1305_tweet_BOXZEROBYTES 16
+extern int crypto_secretbox_xsalsa20poly1305_tweet(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *);
+extern int crypto_secretbox_xsalsa20poly1305_tweet_open(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *);
+#define crypto_secretbox_xsalsa20poly1305_tweet_VERSION "-"
+#define crypto_secretbox_xsalsa20poly1305 crypto_secretbox_xsalsa20poly1305_tweet
+#define crypto_secretbox_xsalsa20poly1305_open crypto_secretbox_xsalsa20poly1305_tweet_open
+#define crypto_secretbox_xsalsa20poly1305_KEYBYTES crypto_secretbox_xsalsa20poly1305_tweet_KEYBYTES
+#define crypto_secretbox_xsalsa20poly1305_NONCEBYTES crypto_secretbox_xsalsa20poly1305_tweet_NONCEBYTES
+#define crypto_secretbox_xsalsa20poly1305_ZEROBYTES crypto_secretbox_xsalsa20poly1305_tweet_ZEROBYTES
+#define crypto_secretbox_xsalsa20poly1305_BOXZEROBYTES crypto_secretbox_xsalsa20poly1305_tweet_BOXZEROBYTES
+#define crypto_secretbox_xsalsa20poly1305_VERSION crypto_secretbox_xsalsa20poly1305_tweet_VERSION
+#define crypto_secretbox_xsalsa20poly1305_IMPLEMENTATION "crypto_secretbox/xsalsa20poly1305/tweet"
+#define crypto_sign_PRIMITIVE "ed25519"
+#define crypto_sign crypto_sign_ed25519
+#define crypto_sign_open crypto_sign_ed25519_open
+#define crypto_sign_keypair crypto_sign_ed25519_keypair
+#define crypto_sign_BYTES crypto_sign_ed25519_BYTES
+#define crypto_sign_PUBLICKEYBYTES crypto_sign_ed25519_PUBLICKEYBYTES
+#define crypto_sign_SECRETKEYBYTES crypto_sign_ed25519_SECRETKEYBYTES
+#define crypto_sign_IMPLEMENTATION crypto_sign_ed25519_IMPLEMENTATION
+#define crypto_sign_VERSION crypto_sign_ed25519_VERSION
+#define crypto_sign_ed25519_tweet_BYTES 64
+#define crypto_sign_ed25519_tweet_PUBLICKEYBYTES 32
+#define crypto_sign_ed25519_tweet_SECRETKEYBYTES 64
+extern int crypto_sign_ed25519_tweet(unsigned char *,unsigned long long *,const unsigned char *,unsigned long long,const unsigned char *);
+extern int crypto_sign_ed25519_tweet_open(unsigned char *,unsigned long long *,const unsigned char *,unsigned long long,const unsigned char *);
+extern int crypto_sign_ed25519_tweet_keypair(unsigned char *,unsigned char *);
+#define crypto_sign_ed25519_tweet_VERSION "-"
+#define crypto_sign_ed25519 crypto_sign_ed25519_tweet
+#define crypto_sign_ed25519_open crypto_sign_ed25519_tweet_open
+#define crypto_sign_ed25519_keypair crypto_sign_ed25519_tweet_keypair
+#define crypto_sign_ed25519_BYTES crypto_sign_ed25519_tweet_BYTES
+#define crypto_sign_ed25519_PUBLICKEYBYTES crypto_sign_ed25519_tweet_PUBLICKEYBYTES
+#define crypto_sign_ed25519_SECRETKEYBYTES crypto_sign_ed25519_tweet_SECRETKEYBYTES
+#define crypto_sign_ed25519_VERSION crypto_sign_ed25519_tweet_VERSION
+#define crypto_sign_ed25519_IMPLEMENTATION "crypto_sign/ed25519/tweet"
+#define crypto_stream_PRIMITIVE "xsalsa20"
+#define crypto_stream crypto_stream_xsalsa20
+#define crypto_stream_xor crypto_stream_xsalsa20_xor
+#define crypto_stream_KEYBYTES crypto_stream_xsalsa20_KEYBYTES
+#define crypto_stream_NONCEBYTES crypto_stream_xsalsa20_NONCEBYTES
+#define crypto_stream_IMPLEMENTATION crypto_stream_xsalsa20_IMPLEMENTATION
+#define crypto_stream_VERSION crypto_stream_xsalsa20_VERSION
+#define crypto_stream_xsalsa20_tweet_KEYBYTES 32
+#define crypto_stream_xsalsa20_tweet_NONCEBYTES 24
+extern int crypto_stream_xsalsa20_tweet(unsigned char *,unsigned long long,const unsigned char *,const unsigned char *);
+extern int crypto_stream_xsalsa20_tweet_xor(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *);
+#define crypto_stream_xsalsa20_tweet_VERSION "-"
+#define crypto_stream_xsalsa20 crypto_stream_xsalsa20_tweet
+#define crypto_stream_xsalsa20_xor crypto_stream_xsalsa20_tweet_xor
+#define crypto_stream_xsalsa20_KEYBYTES crypto_stream_xsalsa20_tweet_KEYBYTES
+#define crypto_stream_xsalsa20_NONCEBYTES crypto_stream_xsalsa20_tweet_NONCEBYTES
+#define crypto_stream_xsalsa20_VERSION crypto_stream_xsalsa20_tweet_VERSION
+#define crypto_stream_xsalsa20_IMPLEMENTATION "crypto_stream/xsalsa20/tweet"
+#define crypto_stream_salsa20_tweet_KEYBYTES 32
+#define crypto_stream_salsa20_tweet_NONCEBYTES 8
+extern int crypto_stream_salsa20_tweet(unsigned char *,unsigned long long,const unsigned char *,const unsigned char *);
+extern int crypto_stream_salsa20_tweet_xor(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *);
+#define crypto_stream_salsa20_tweet_VERSION "-"
+#define crypto_stream_salsa20 crypto_stream_salsa20_tweet
+#define crypto_stream_salsa20_xor crypto_stream_salsa20_tweet_xor
+#define crypto_stream_salsa20_KEYBYTES crypto_stream_salsa20_tweet_KEYBYTES
+#define crypto_stream_salsa20_NONCEBYTES crypto_stream_salsa20_tweet_NONCEBYTES
+#define crypto_stream_salsa20_VERSION crypto_stream_salsa20_tweet_VERSION
+#define crypto_stream_salsa20_IMPLEMENTATION "crypto_stream/salsa20/tweet"
+#define crypto_verify_PRIMITIVE "16"
+#define crypto_verify crypto_verify_16
+#define crypto_verify_BYTES crypto_verify_16_BYTES
+#define crypto_verify_IMPLEMENTATION crypto_verify_16_IMPLEMENTATION
+#define crypto_verify_VERSION crypto_verify_16_VERSION
+#define crypto_verify_16_tweet_BYTES 16
+extern int crypto_verify_16_tweet(const unsigned char *,const unsigned char *);
+#define crypto_verify_16_tweet_VERSION "-"
+#define crypto_verify_16 crypto_verify_16_tweet
+#define crypto_verify_16_BYTES crypto_verify_16_tweet_BYTES
+#define crypto_verify_16_VERSION crypto_verify_16_tweet_VERSION
+#define crypto_verify_16_IMPLEMENTATION "crypto_verify/16/tweet"
+#define crypto_verify_32_tweet_BYTES 32
+extern int crypto_verify_32_tweet(const unsigned char *,const unsigned char *);
+#define crypto_verify_32_tweet_VERSION "-"
+#define crypto_verify_32 crypto_verify_32_tweet
+#define crypto_verify_32_BYTES crypto_verify_32_tweet_BYTES
+#define crypto_verify_32_VERSION crypto_verify_32_tweet_VERSION
+#define crypto_verify_32_IMPLEMENTATION "crypto_verify/32/tweet"
+#endif