├── .gitignore ├── pfSense-pkg-FauxAPI ├── files │ ├── pkg-deinstall.in │ ├── pkg-install.in │ ├── usr │ │ └── local │ │ │ ├── share │ │ │ └── pfSense-pkg-FauxAPI │ │ │ │ └── info.xml │ │ │ ├── www │ │ │ └── fauxapi │ │ │ │ ├── v1 │ │ │ │ └── index.php │ │ │ │ └── admin │ │ │ │ ├── logs.php │ │ │ │ └── credentials.php │ │ │ └── pkg │ │ │ └── fauxapi.xml │ └── etc │ │ ├── inc │ │ ├── priv │ │ │ └── fauxapi.priv.inc │ │ └── fauxapi │ │ │ ├── fauxapi_logger.inc │ │ │ ├── fauxapi_utils.inc │ │ │ ├── fauxapi_auth.inc │ │ │ ├── fauxapi.inc │ │ │ ├── fauxapi_actions.inc │ │ │ └── fauxapi_pfsense_interface.inc │ │ └── fauxapi │ │ └── credentials.sample.ini ├── pkg-descr ├── pkg-plist └── Makefile ├── extras ├── test-tools │ ├── pyvboxmanage-pfsense-2.3.2.yml │ ├── pyvboxmanage-pfsense-2.3.3.yml │ ├── pyvboxmanage-pfsense-2.3.4.yml │ ├── pyvboxmanage-pfsense-2.4.3.yml │ ├── pyvboxmanage-pfsense-2.4.4.yml │ ├── pyvboxmanage-pfsense-2.4.5.yml │ ├── pyvboxmanage-pfsense-2.5.0.yml │ ├── pyvboxmanage-pfsense-2.5.1.yml │ ├── README.md │ └── pyvboxmanage-pfsense-base.yml ├── dev-tools │ ├── devhost-deinstall.sh │ ├── credentials.ini │ └── devhost-install.sh └── build-tools │ ├── build-at-remote.sh │ ├── update-fauxapi-about.sh │ ├── instances-reclone.sh │ └── README.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /_dev 2 | /.idea 3 | /nbproject 4 | *.pyc 5 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/pkg-deinstall.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | /usr/local/bin/php -f /etc/rc.packages %%PORTNAME%% ${2} -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/pkg-descr: -------------------------------------------------------------------------------- 1 | A REST API interface for pfSense to facilitate dev-ops. 2 | 3 | WWW: https://github.com/ndejong/pfsense_fauxapi -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/pkg-install.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "${2}" != "POST-INSTALL" ]; then 4 | exit 0 5 | fi 6 | 7 | /usr/local/bin/php -f /etc/rc.packages %%PORTNAME%% ${2} -------------------------------------------------------------------------------- /extras/test-tools/pyvboxmanage-pfsense-2.3.2.yml: -------------------------------------------------------------------------------- 1 | 2 | vars: 3 | iso_path: "/opt/storage/operating_systems/pfsense" 4 | iso_filename: "pfSense-CE-2.3.2-RELEASE-amd64.iso" 5 | 6 | target_vmname: "pfSense-CE-2.3.2" 7 | target_basefolder: "/opt/virtual-machines" 8 | target_groups: "/pfsense" 9 | target_bridgeadapter: "enp0s25" # local NIC to bridge from 10 | vmhost_nic_prefix: "0800000232" # first 5x octets only 11 | 12 | imports: 13 | - 'pyvboxmanage-pfsense-base.yml' 14 | -------------------------------------------------------------------------------- /extras/test-tools/pyvboxmanage-pfsense-2.3.3.yml: -------------------------------------------------------------------------------- 1 | 2 | vars: 3 | iso_path: "/opt/storage/operating_systems/pfsense" 4 | iso_filename: "pfSense-CE-2.3.3-RELEASE-amd64.iso" 5 | 6 | target_vmname: "pfSense-CE-2.3.3" 7 | target_basefolder: "/opt/virtual-machines" 8 | target_groups: "/pfsense" 9 | target_bridgeadapter: "enp0s25" # local NIC to bridge from 10 | vmhost_nic_prefix: "0800000233" # first 5x octets only 11 | 12 | imports: 13 | - 'pyvboxmanage-pfsense-base.yml' 14 | -------------------------------------------------------------------------------- /extras/test-tools/pyvboxmanage-pfsense-2.3.4.yml: -------------------------------------------------------------------------------- 1 | 2 | vars: 3 | iso_path: "/opt/storage/operating_systems/pfsense" 4 | iso_filename: "pfSense-CE-2.3.4-RELEASE-amd64.iso" 5 | 6 | target_vmname: "pfSense-CE-2.3.4" 7 | target_basefolder: "/opt/virtual-machines" 8 | target_groups: "/pfsense" 9 | target_bridgeadapter: "enp0s25" # local NIC to bridge from 10 | vmhost_nic_prefix: "0800000234" # first 5x octets only 11 | 12 | imports: 13 | - 'pyvboxmanage-pfsense-base.yml' 14 | -------------------------------------------------------------------------------- /extras/test-tools/pyvboxmanage-pfsense-2.4.3.yml: -------------------------------------------------------------------------------- 1 | 2 | vars: 3 | iso_path: "/opt/storage/operating_systems/pfsense" 4 | iso_filename: "pfSense-CE-2.4.3-RELEASE-amd64.iso" 5 | 6 | target_vmname: "pfSense-CE-2.4.3" 7 | target_basefolder: "/opt/virtual-machines" 8 | target_groups: "/pfsense" 9 | target_bridgeadapter: "enp0s25" # local NIC to bridge from 10 | vmhost_nic_prefix: "0800000243" # first 5x octets only 11 | 12 | imports: 13 | - 'pyvboxmanage-pfsense-base.yml' 14 | -------------------------------------------------------------------------------- /extras/test-tools/pyvboxmanage-pfsense-2.4.4.yml: -------------------------------------------------------------------------------- 1 | 2 | vars: 3 | iso_path: "/opt/storage/operating_systems/pfsense" 4 | iso_filename: "pfSense-CE-2.4.4-RELEASE-amd64.iso" 5 | 6 | target_vmname: "pfSense-CE-2.4.4" 7 | target_basefolder: "/opt/virtual-machines" 8 | target_groups: "/pfsense" 9 | target_bridgeadapter: "enp0s25" # local NIC to bridge from 10 | vmhost_nic_prefix: "0800000244" # first 5x octets only 11 | 12 | imports: 13 | - 'pyvboxmanage-pfsense-base.yml' 14 | -------------------------------------------------------------------------------- /extras/test-tools/pyvboxmanage-pfsense-2.4.5.yml: -------------------------------------------------------------------------------- 1 | 2 | vars: 3 | iso_path: "/opt/storage/operating_systems/pfsense" 4 | iso_filename: "pfSense-CE-2.4.5-RELEASE-amd64.iso" 5 | 6 | target_vmname: "pfSense-CE-2.4.5" 7 | target_basefolder: "/opt/virtual-machines" 8 | target_groups: "/pfsense" 9 | target_bridgeadapter: "enp0s25" # local NIC to bridge from 10 | vmhost_nic_prefix: "0800000245" # first 5x octets only 11 | 12 | imports: 13 | - 'pyvboxmanage-pfsense-base.yml' 14 | -------------------------------------------------------------------------------- /extras/test-tools/pyvboxmanage-pfsense-2.5.0.yml: -------------------------------------------------------------------------------- 1 | 2 | vars: 3 | iso_path: "/opt/storage/operating_systems/pfsense" 4 | iso_filename: "pfSense-CE-2.5.0-RELEASE-amd64.iso" 5 | 6 | target_vmname: "pfSense-CE-2.5.0" 7 | target_basefolder: "/opt/virtual-machines" 8 | target_groups: "/pfsense" 9 | target_bridgeadapter: "enp0s25" # local NIC to bridge from 10 | vmhost_nic_prefix: "0800000250" # first 5x octets only 11 | 12 | imports: 13 | - 'pyvboxmanage-pfsense-base.yml' 14 | -------------------------------------------------------------------------------- /extras/test-tools/pyvboxmanage-pfsense-2.5.1.yml: -------------------------------------------------------------------------------- 1 | 2 | vars: 3 | iso_path: "/opt/storage/operating_systems/pfsense" 4 | iso_filename: "pfSense-CE-2.5.1-RELEASE-amd64.iso" 5 | 6 | target_vmname: "pfSense-CE-2.5.1" 7 | target_basefolder: "/opt/virtual-machines" 8 | target_groups: "/pfsense" 9 | target_bridgeadapter: "enp0s25" # local NIC to bridge from 10 | vmhost_nic_prefix: "0800000251" # first 5x octets only 11 | 12 | imports: 13 | - 'pyvboxmanage-pfsense-base.yml' 14 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/usr/local/share/pfSense-pkg-FauxAPI/info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FauxAPI 5 | fauxapi 6 | 7 | https://github.com/ndejong/pfsense_fauxapi 8 | System 9 | %%PKGVERSION%% 10 | beta 11 | 2.3.x, 2.4.x 12 | fauxapi.xml 13 | contact@nicholasdejong.com 14 | 15 | 16 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/pkg-plist: -------------------------------------------------------------------------------- 1 | @dir /usr/local/pkg 2 | /usr/local/pkg/fauxapi.xml 3 | 4 | @dir /etc/inc/priv 5 | /etc/inc/priv/fauxapi.priv.inc 6 | 7 | @dir /etc/fauxapi 8 | /etc/fauxapi/credentials.sample.ini 9 | /etc/fauxapi/pfsense_function_calls.sample.txt 10 | 11 | @dir /usr/local/www/fauxapi 12 | @dir /usr/local/www/fauxapi/v1 13 | /usr/local/www/fauxapi/v1/index.php 14 | 15 | @dir /usr/local/www/fauxapi/admin 16 | /usr/local/www/fauxapi/admin/about.php 17 | /usr/local/www/fauxapi/admin/credentials.php 18 | /usr/local/www/fauxapi/admin/logs.php 19 | 20 | @dir /etc/inc/fauxapi 21 | /etc/inc/fauxapi/fauxapi.inc 22 | /etc/inc/fauxapi/fauxapi_actions.inc 23 | /etc/inc/fauxapi/fauxapi_auth.inc 24 | /etc/inc/fauxapi/fauxapi_logger.inc 25 | /etc/inc/fauxapi/fauxapi_pfsense_interface.inc 26 | /etc/inc/fauxapi/fauxapi_utils.inc 27 | 28 | %%DATADIR%%/info.xml 29 | -------------------------------------------------------------------------------- /extras/dev-tools/devhost-deinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | remote_host=${1} 4 | remote_user=root 5 | 6 | if [ -z ${remote_host} ]; then 7 | echo 'usage: '$0' ' 8 | exit 1 9 | fi 10 | 11 | PORTNAME=pfSense-pkg-FauxAPI 12 | PREFIX=usr/local 13 | DATADIR=${PREFIX}/share/${PORTNAME} 14 | 15 | ssh $remote_user@$remote_host "/usr/local/bin/php -f /etc/rc.packages ${PORTNAME} DEINSTALL" 16 | 17 | ssh $remote_user@$remote_host "rm -Rf /${DATADIR}" 18 | ssh $remote_user@$remote_host "rm -Rf /${PREFIX}/pkg/fauxapi.xml" 19 | ssh $remote_user@$remote_host "rm -Rf /etc/inc/priv/fauxapi.priv.inc" 20 | ssh $remote_user@$remote_host "rm -Rf /etc/fauxapi" 21 | ssh $remote_user@$remote_host "rm -Rf /etc/inc/fauxapi" 22 | ssh $remote_user@$remote_host "rm -Rf /cf/conf/fauxapi" 23 | ssh $remote_user@$remote_host "rm -Rf /${PREFIX}/www/fauxapi" 24 | 25 | ssh $remote_user@$remote_host "/usr/local/bin/php -f /etc/rc.packages ${PORTNAME} POST-DEINSTALL" 26 | -------------------------------------------------------------------------------- /extras/build-tools/build-at-remote.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | remote_host=${1} 4 | remote_user=root 5 | local_base_path=$(realpath $(dirname ${0})/../../) 6 | local_packages_path=$(realpath ${local_base_path}/../pfsense_fauxapi_packages) 7 | 8 | if [ -z ${remote_host} ]; then 9 | echo "usage: ${0} " 10 | exit 1 11 | fi 12 | 13 | PORTNAME=pfSense-pkg-FauxAPI 14 | STAGEDIR="${remote_user}@${remote_host}:/" 15 | 16 | # push the code to the remote FreeBSD system 17 | rsync -rv --delete ${local_base_path}/${PORTNAME}/ ${STAGEDIR}/usr/ports/sysutils/${PORTNAME} 18 | 19 | # do the build 20 | ssh $remote_user@$remote_host "cd /usr/ports/sysutils/${PORTNAME}; make clean; make package" 21 | 22 | # pull the .txz packages back 23 | rsync --ignore-existing $remote_user@$remote_host:/usr/ports/sysutils/${PORTNAME}/work/pkg/*.txz ${local_packages_path} 24 | 25 | # re-roll the SHA256SUMS 26 | cd ${local_packages_path} 27 | sha256sum pfSense-pkg-FauxAPI-*.txz > SHA256SUMS 28 | -------------------------------------------------------------------------------- /extras/build-tools/update-fauxapi-about.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source_md_filename=$(realpath $(dirname $(realpath $0))/../../README.md) 4 | target_php_filename=$(realpath $(dirname $(realpath $0))/../../pfSense-pkg-FauxAPI/files/usr/local/www/fauxapi/admin/about.php) 5 | 6 | # === 7 | 8 | # render markdown into html at github 9 | jq --slurp --raw-input '{"text": "\(.)", "mode": "markdown"}' < $source_md_filename | curl --silent --data @- https://api.github.com/markdown > /tmp/fauxapi-about.content 10 | 11 | # head 12 | head -n `grep -n READMESTART ${target_php_filename} | cut -d':' -f1` ${target_php_filename} > /tmp/fauxapi-about.head 13 | 14 | # tail 15 | tail -n +`grep -n READMEEND ${target_php_filename} | cut -d':' -f1` ${target_php_filename} > /tmp/fauxapi-about.tail 16 | 17 | # re-create 18 | cat /tmp/fauxapi-about.head > /tmp/fauxapi-about.php 19 | cat /tmp/fauxapi-about.content >> /tmp/fauxapi-about.php 20 | cat /tmp/fauxapi-about.tail >> /tmp/fauxapi-about.php 21 | 22 | # move the result file into place and clean up 23 | mv /tmp/fauxapi-about.php ${target_php_filename} 24 | rm -f /tmp/fauxapi-about.* 25 | -------------------------------------------------------------------------------- /extras/test-tools/README.md: -------------------------------------------------------------------------------- 1 | # pfSense FauxAPI - test tools 2 | 3 | Configuration files for `pyvboxmanage` to re-create pfSense images from ISO sources that 4 | can then be used testing pfSense-FauxAPI against various pfSense versions. 5 | 6 | ## Example 7 | NB: this will **CLOBBER** any existing VM as configured in the .yml 8 | ```shell 9 | user@computer:~$ pyvboxmanage pyvboxmanage-pfsense-2.3.2.yml 10 | ``` 11 | 12 | ## PyVBoxManage 13 | - https://pyvboxmanage.readthedocs.io/en/latest/docs/configuration-file/ 14 | 15 | ### Install PyVBoxManage 16 | ```shell 17 | user@computer:~$ pip install pyvboxmanage 18 | ``` 19 | 20 | ## ISO image SHA1 21 | ```text 22 | 009abcebc2fee1a57a61631b9cd3d9fe5ac33f37 pfSense-CE-2.3.2-RELEASE-amd64.iso 23 | d634876139113eae1211805bb296b6668a6a1b0a pfSense-CE-2.3.3-RELEASE-amd64.iso 24 | eca64393f8da74e51028557c6470f0b1ae2994f4 pfSense-CE-2.3.4-RELEASE-amd64.iso 25 | 582160dc3e4b3b7b8de90fd1c7cb1247219505ae pfSense-CE-2.4.3-RELEASE-amd64.iso 26 | d07de5bde954876bab1a6391913cf3de37c83fd7 pfSense-CE-2.4.4-RELEASE-amd64.iso 27 | c1d9a4e36deecdbd280f75881e52d90954435996 pfSense-CE-2.4.5-RELEASE-amd64.iso 28 | 98387ad66ed4800f864cded655439f4bd351c9b5 pfSense-CE-2.5.0-RELEASE-amd64.iso 29 | fc61a85f13e6390dd2c18eb9049df366b701b814 pfSense-CE-2.5.1-RELEASE-amd64.iso 30 | ``` 31 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/etc/inc/priv/fauxapi.priv.inc: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/usr/local/www/fauxapi/v1/index.php: -------------------------------------------------------------------------------- 1 | $action($_GET, file_get_contents("php://input")); 33 | 34 | http_response_code($response->http_code); 35 | if(!empty($response->action)) { 36 | header('fauxapi-callid: ' . FAUXAPI_CALLID); 37 | } 38 | header('Content-Type: application/json'); 39 | 40 | unset($response->http_code); 41 | echo json_encode($response); 42 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/etc/fauxapi/credentials.sample.ini: -------------------------------------------------------------------------------- 1 | ;; FauxAPI credentials 2 | ;; 3 | ;; format:- 4 | ;; 5 | ;; [] 6 | ;; secret = 7 | ;; comment = 8 | ;; permit = 9 | ;; 10 | ;; 11 | ;; NB1: and must have alphanumeric chars ONLY! 12 | ;; be sure to remove /+= chars possible from a naive base64encode call 13 | ;; NB2: MUST start with the prefix PFFA (ie. pfSense Faux API) 14 | ;; NB3: MUST be >= 12 chars AND <= 40 chars in total length 15 | ;; NB4: MUST be >= 40 chars AND <= 128 chars in length 16 | ;; NB5: provides no function other than display 17 | ;; NB6: wildcard * character may be used to construct action matches 18 | ;; 19 | ;; Generate a valid using the following command line example:- 20 | ;; $ echo PFFA`head /dev/urandom | base64 -w0 | tr -d /+= | head -c 20` 21 | ;; 22 | ;; Generate a valid using the following command line example:- 23 | ;; $ echo `head /dev/urandom | base64 -w0 | tr -d /+= | head -c 60` 24 | ;; 25 | 26 | ;; PFFAexample01 is hardcoded to be inoperative 27 | [PFFAexample01] 28 | secret = abcdefghijklmnopqrstuvwxyz0123456789abcd 29 | permit = alias_*, config_*, gateway_*, rule_*, send_*, system_*, function_* 30 | comment = example key PFFAexample01 - hardcoded to be inoperative 31 | 32 | ;; PFFAexample02 is hardcoded to be inoperative 33 | [PFFAexample02] 34 | secret = ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCD 35 | permit = * 36 | comment = example key PFFAexample02 - hardcoded to be inoperative 37 | -------------------------------------------------------------------------------- /extras/dev-tools/credentials.ini: -------------------------------------------------------------------------------- 1 | ;; FauxAPI credentials 2 | ;; 3 | ;; format:- 4 | ;; 5 | ;; [] 6 | ;; secret = 7 | ;; comment = 8 | ;; permit = 9 | ;; 10 | ;; 11 | ;; NB1: and must have alphanumeric chars ONLY! 12 | ;; be sure to remove /+= chars possible from a naive base64encode call 13 | ;; NB2: MUST start with the prefix PFFA (ie. pfSense Faux API) 14 | ;; NB3: MUST be >= 12 chars AND <= 40 chars in total length 15 | ;; NB4: MUST be >= 40 chars AND <= 128 chars in length 16 | ;; NB5: provides no function other than display 17 | ;; NB6: wildcard * character may be used to construct action matches 18 | ;; 19 | ;; Generate a valid using the following command line example:- 20 | ;; $ echo PFFA`head /dev/urandom | base64 -w0 | tr -d /+= | head -c 20` 21 | ;; 22 | ;; Generate a valid using the following command line example:- 23 | ;; $ echo `head /dev/urandom | base64 -w0 | tr -d /+= | head -c 60` 24 | ;; 25 | 26 | ;; PFFAexample01 is hardcoded to be inoperative 27 | [PFFAexample01] 28 | secret = abcdefghijklmnopqrstuvwxyz0123456789abcd 29 | permit = alias_*, config_*, gateway_*, rule_*, send_*, system_*, function_* 30 | comment = example key PFFAexample01 - hardcoded to be inoperative 31 | 32 | ;; PFFAexample02 is hardcoded to be inoperative 33 | [PFFAexample02] 34 | secret = ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCD 35 | permit = * 36 | comment = example key PFFAexample02 - hardcoded to be inoperative 37 | 38 | [PFFAdevtrash] 39 | secret = devtrashdevtrashdevtrashdevtrashdevtrash 40 | permit = * 41 | comment = development only local user 42 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/usr/local/pkg/fauxapi.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 27 | 28 | A REST API interface 29 | FauxAPI 30 | System: FauxAPI 31 | 32 | FauxAPI 33 | A REST API interface for pfSense to facilitate dev-ops. 34 |
System
35 | /fauxapi/admin/credentials.php 36 |
37 | 38 | 39 | API credentials 40 | /fauxapi/admin/credentials.php 41 | 42 | 43 | 44 | Logs 45 | /fauxapi/admin/logs.php 46 | 47 | 48 | 49 | About 50 | /fauxapi/admin/about.php 51 | 52 | 53 |
-------------------------------------------------------------------------------- /extras/build-tools/instances-reclone.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | group_name="pfsense" 4 | source_vm="pfSense-builder-FreeBSD-11.3-master" 5 | target_folder="/opt/virtual-machines" 6 | 7 | function clone_instance() { 8 | 9 | function usage() { 10 | echo "Usage: clone_instance -n [-a ] [-b ] [-p poweron ]" 1>&2; 11 | exit 1; 12 | } 13 | 14 | local OPTIND option n a b p 15 | 16 | while getopts ":n:a:b:p:" option; do 17 | case "${option}" in 18 | n) 19 | node_name=${OPTARG} 20 | ;; 21 | a) 22 | macaddress1=${OPTARG:-""} 23 | ;; 24 | b) 25 | macaddress2=${OPTARG:-""} 26 | ;; 27 | p) 28 | poweron=${OPTARG:-""} 29 | ;; 30 | *) 31 | usage 32 | ;; 33 | esac 34 | done 35 | shift $((OPTIND-1)) 36 | 37 | if [ -z "${node_name}" ]; then 38 | usage 39 | fi 40 | 41 | # ==== 42 | 43 | __running_count=`vboxmanage showvminfo "${node_name}" | grep "^State" | grep "running" | wc -l` 44 | 45 | if [[ ${__running_count} -gt 0 ]]; then 46 | echo "${node_name} is powered on, skipping" 47 | return 0 48 | fi 49 | echo "${node_name} is being cloned from ${source_vm}" 50 | 51 | if [[ -d "${target_folder}/${group_name}/${node_name}" ]]; then 52 | vboxmanage unregistervm "${node_name}" 53 | rm -Rf "${target_folder}/${group_name}/${node_name}" 54 | fi 55 | vboxmanage clonevm "${source_vm}" --basefolder "${target_folder}" --register --mode machine --groups "/${group_name}" --name "${node_name}" 56 | 57 | if [[ -z "${macaddress1}" ]]; then 58 | vboxmanage modifyvm "${node_name}" --nic1 null 59 | vboxmanage modifyvm "${node_name}" --cableconnected1 off 60 | else 61 | vboxmanage modifyvm "${node_name}" --macaddress1 "${macaddress1}" 62 | fi 63 | 64 | if [[ -z "${macaddress2}" ]]; then 65 | vboxmanage modifyvm "${node_name}" --nic2 null 66 | vboxmanage modifyvm "${node_name}" --cableconnected2 off 67 | else 68 | vboxmanage modifyvm "${node_name}" --macaddress2 "${macaddress2}" 69 | fi 70 | 71 | if [[ ${poweron} == "poweron" ]]; then 72 | vboxmanage startvm "${node_name}" --type gui 73 | fi 74 | 75 | echo "" 76 | return 1 77 | } 78 | 79 | clone_instance -n "${group_name}-builder1" -a 08002722E841 -p poweron 80 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/Makefile: -------------------------------------------------------------------------------- 1 | # $FreeBSD$ 2 | 3 | PORTNAME= pfSense-pkg-FauxAPI 4 | PORTVERSION= 1.4 5 | PORTREVISION= 1 6 | CATEGORIES= sysutils 7 | MASTER_SITES= # empty 8 | DISTFILES= # empty 9 | EXTRACT_ONLY= # empty 10 | 11 | MAINTAINER= contact@nicholasdejong.com 12 | COMMENT= pfSense package FauxAPI 13 | 14 | LICENSE= APACHE20 15 | 16 | NO_BUILD= yes 17 | NO_MTREE= yes 18 | 19 | SUB_FILES= pkg-install pkg-deinstall 20 | SUB_LIST= PORTNAME=${PORTNAME} 21 | 22 | do-extract: 23 | ${MKDIR} ${WRKSRC} 24 | 25 | do-install: 26 | ${MKDIR} ${STAGEDIR}${PREFIX}/pkg 27 | ${INSTALL_DATA} -m 0644 ${FILESDIR}${PREFIX}/pkg/fauxapi.xml \ 28 | ${STAGEDIR}${PREFIX}/pkg 29 | 30 | ${MKDIR} ${STAGEDIR}/etc/inc/priv 31 | ${INSTALL_DATA} ${FILESDIR}/etc/inc/priv/fauxapi.priv.inc \ 32 | ${STAGEDIR}/etc/inc/priv 33 | 34 | ${MKDIR} ${STAGEDIR}/etc/fauxapi 35 | ${INSTALL_DATA} -m 0600 ${FILESDIR}/etc/fauxapi/credentials.sample.ini \ 36 | ${STAGEDIR}/etc/fauxapi 37 | ${INSTALL_DATA} -m 0644 ${FILESDIR}/etc/fauxapi/pfsense_function_calls.sample.txt \ 38 | ${STAGEDIR}/etc/fauxapi 39 | 40 | ${MKDIR} ${STAGEDIR}${PREFIX}/www/fauxapi 41 | ${MKDIR} ${STAGEDIR}${PREFIX}/www/fauxapi/v1 42 | ${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/fauxapi/v1/index.php \ 43 | ${STAGEDIR}${PREFIX}/www/fauxapi/v1 44 | 45 | ${MKDIR} ${STAGEDIR}${PREFIX}/www/fauxapi/admin 46 | ${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/fauxapi/admin/about.php \ 47 | ${STAGEDIR}${PREFIX}/www/fauxapi/admin 48 | ${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/fauxapi/admin/credentials.php \ 49 | ${STAGEDIR}${PREFIX}/www/fauxapi/admin 50 | ${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/fauxapi/admin/logs.php \ 51 | ${STAGEDIR}${PREFIX}/www/fauxapi/admin 52 | 53 | ${MKDIR} ${STAGEDIR}/etc/inc/fauxapi 54 | ${INSTALL_DATA} ${FILESDIR}/etc/inc/fauxapi/fauxapi.inc \ 55 | ${STAGEDIR}/etc/inc/fauxapi 56 | ${INSTALL_DATA} ${FILESDIR}/etc/inc/fauxapi/fauxapi_actions.inc \ 57 | ${STAGEDIR}/etc/inc/fauxapi 58 | ${INSTALL_DATA} ${FILESDIR}/etc/inc/fauxapi/fauxapi_auth.inc \ 59 | ${STAGEDIR}/etc/inc/fauxapi 60 | ${INSTALL_DATA} ${FILESDIR}/etc/inc/fauxapi/fauxapi_logger.inc \ 61 | ${STAGEDIR}/etc/inc/fauxapi 62 | ${INSTALL_DATA} ${FILESDIR}/etc/inc/fauxapi/fauxapi_pfsense_interface.inc \ 63 | ${STAGEDIR}/etc/inc/fauxapi 64 | ${INSTALL_DATA} ${FILESDIR}/etc/inc/fauxapi/fauxapi_utils.inc \ 65 | ${STAGEDIR}/etc/inc/fauxapi 66 | 67 | ${MKDIR} ${STAGEDIR}${DATADIR} 68 | ${INSTALL_DATA} ${FILESDIR}${DATADIR}/info.xml \ 69 | ${STAGEDIR}${DATADIR} 70 | @${REINPLACE_CMD} -i '' -e "s|%%PKGVERSION%%|${PKGVERSION}|" \ 71 | ${STAGEDIR}${DATADIR}/info.xml 72 | 73 | .include 74 | -------------------------------------------------------------------------------- /extras/build-tools/README.md: -------------------------------------------------------------------------------- 1 | # Build Notes 2 | 3 | NB: there does not seem to be any requirement for the FreeBSD host used to build 4 | packages with to be the same FreeBSD (or pfSense) version that you are 5 | targeting - I note this here because this was not clear to myself when starting 6 | to build packages. 7 | 8 | ## Create a fresh FreeBSD host based on VirtualBox 9 | * Obtain a FreeBSD `VM-IMAGE` based release https://download.freebsd.org/ftp/releases/VM-IMAGES/ 10 | * Recommended image type is VMDK which seems to work well with VirtualBox 11 | * Decompress the VMDK `xz`, create a new VirtualBox using this HDD image and power-on 12 | * Login to the instance at the console (root, no password) 13 | * Perform a package update `pkg update` 14 | * Install packages you will need `pkg install rsync` 15 | * Enable sshd at reboot by adding `sshd_enable="YES"` to the file `/etc/rc.conf` 16 | * Enable root login via ssh (if this approach suits you) 17 | * Consider adding your ssh key(s) to login user (again, if this approach suits you) 18 | * Powerdown instance and move VirtualBox (plus `vmdk`) file to a location that suits 19 | 20 | ## Create a VirtualBox clone to work with 21 | * Using the `instances-reclone.sh` script, edit the paths in this script (in the upper section) 22 | * Consider changing the MAC addresses in the (in the lower section) of the `instances-reclone.sh` script 23 | * Run the `instances-reclone.sh` to get a "nice" fresh clone - this tool will **CLOBBER** the target image if target is in powered-off state! 24 | 25 | ## Pull down the pfSense FreeBSD-ports locally to build against 26 | * Install git and pull down the latest pfSense FreeBSD-ports fork 27 | ```bash 28 | pkg install git 29 | cd /usr/ 30 | git clone https://github.com/pfsense/FreeBSD-ports ports 31 | ``` 32 | * Consider creating a VirtualBox image based on the fresh FreeBSD + FreeBSD-ports to make a reclone easier/quicker later on. 33 | 34 | ## Build a package 35 | * Clean the build path if required before a build 36 | ```bash 37 | cd /path/to/package 38 | make clean 39 | ``` 40 | 41 | * Change to the package directory and make 42 | ```bash 43 | cd /path/to/package 44 | make package 45 | ``` 46 | 47 | * At end of the build process you should have a `.txz` package file ready for installation on a pfSense host 48 | 49 | ## Checking for errors 50 | * Before submitting a package you should check with `portlint` 51 | ```bash 52 | pkg install portlint 53 | echo DEVELOPER=yes >> /etc/make.conf 54 | cd /path/to/package 55 | portlint -CN 56 | ``` 57 | 58 | ## Various pointers and other notes 59 | * https://forum.pfsense.org/index.php?topic=112807.0 60 | * https://gist.github.com/jdillard/3f44d06ba616fec60890488abfd7e5f5 61 | -------------------------------------------------------------------------------- /extras/dev-tools/devhost-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | remote_host=${1} 4 | remote_user=root 5 | local_base_path=$(realpath $(dirname $(realpath $0))/../../) 6 | 7 | if [ -z ${remote_host} ]; then 8 | echo 'usage: '${0}' ' 9 | exit 1 10 | fi 11 | 12 | PORTNAME=pfSense-pkg-FauxAPI 13 | FILESDIR=${local_base_path}/${PORTNAME}/files/ 14 | PREFIX=usr/local 15 | DATADIR=${PREFIX}/share/${PORTNAME} 16 | STAGEDIR=$remote_user@$remote_host:/ 17 | 18 | ssh $remote_user@$remote_host " \ 19 | mkdir -p /${DATADIR}; \ 20 | mkdir -p /${PREFIX}/pkg; \ 21 | mkdir -p /etc/inc/priv; \ 22 | mkdir -p /etc/fauxapi; \ 23 | mkdir -p /etc/inc/fauxapi; \ 24 | mkdir -p /${PREFIX}/www/fauxapi/v1; \ 25 | mkdir -p /${PREFIX}/www/fauxapi/admin; \ 26 | " 27 | 28 | scp ${FILESDIR}${PREFIX}/pkg/fauxapi.xml \ 29 | ${STAGEDIR}${PREFIX}/pkg 30 | 31 | scp ${FILESDIR}/etc/inc/priv/fauxapi.priv.inc \ 32 | ${STAGEDIR}/etc/inc/priv 33 | 34 | scp ${FILESDIR}/etc/fauxapi/credentials.sample.ini \ 35 | ${STAGEDIR}/etc/fauxapi 36 | scp ${local_base_path}/extras/dev-tools/credentials.ini \ 37 | ${STAGEDIR}/etc/fauxapi 38 | 39 | scp ${FILESDIR}/etc/fauxapi/pfsense_function_calls.sample.txt \ 40 | ${STAGEDIR}/etc/fauxapi 41 | scp ${local_base_path}/extras/dev-tools/pfsense_function_calls.txt \ 42 | ${STAGEDIR}/etc/fauxapi 43 | 44 | scp ${FILESDIR}${PREFIX}/www/fauxapi/v1/index.php \ 45 | ${STAGEDIR}${PREFIX}/www/fauxapi/v1 46 | 47 | scp ${FILESDIR}${PREFIX}/www/fauxapi/admin/about.php \ 48 | ${STAGEDIR}${PREFIX}/www/fauxapi/admin 49 | 50 | scp ${FILESDIR}${PREFIX}/www/fauxapi/admin/credentials.php \ 51 | ${STAGEDIR}${PREFIX}/www/fauxapi/admin 52 | 53 | scp ${FILESDIR}${PREFIX}/www/fauxapi/admin/logs.php \ 54 | ${STAGEDIR}${PREFIX}/www/fauxapi/admin 55 | 56 | scp ${FILESDIR}/etc/inc/fauxapi/fauxapi.inc \ 57 | ${STAGEDIR}/etc/inc/fauxapi 58 | 59 | scp ${FILESDIR}/etc/inc/fauxapi/fauxapi_actions.inc \ 60 | ${STAGEDIR}/etc/inc/fauxapi 61 | 62 | scp ${FILESDIR}/etc/inc/fauxapi/fauxapi_auth.inc \ 63 | ${STAGEDIR}/etc/inc/fauxapi 64 | 65 | scp ${FILESDIR}/etc/inc/fauxapi/fauxapi_logger.inc \ 66 | ${STAGEDIR}/etc/inc/fauxapi 67 | 68 | scp ${FILESDIR}/etc/inc/fauxapi/fauxapi_pfsense_interface.inc \ 69 | ${STAGEDIR}/etc/inc/fauxapi 70 | 71 | scp ${FILESDIR}/etc/inc/fauxapi/fauxapi_utils.inc \ 72 | ${STAGEDIR}/etc/inc/fauxapi 73 | 74 | scp ${FILESDIR}${DATADIR}/info.xml \ 75 | ${STAGEDIR}${DATADIR} 76 | 77 | ssh $remote_user@$remote_host "/usr/local/bin/php -f /etc/rc.packages ${PORTNAME} POST-INSTALL" 78 | -------------------------------------------------------------------------------- /extras/test-tools/pyvboxmanage-pfsense-base.yml: -------------------------------------------------------------------------------- 1 | 2 | # Command opts and args follow https://www.virtualbox.org/manual/ch08.html docs 3 | pyvboxmanage: 4 | 5 | - showvminfo: 6 | args: 7 | - '{target_vmname}' 8 | triggers: 9 | - source: stdout 10 | string: running 11 | condition: not present 12 | returncodes: [0,1] 13 | 14 | - unregistervm: 15 | args: '{target_vmname}' 16 | opts: 17 | delete: true 18 | returncodes: [0,1] 19 | 20 | - closemedium: 21 | args: 22 | - "disk" 23 | - "{target_basefolder}{target_groups}/{target_vmname}/{target_vmname}.vdi" 24 | opts: 25 | delete: true 26 | returncodes: [0,1] 27 | 28 | - createvm: 29 | opts: 30 | name: '{target_vmname}' 31 | basefolder: '{target_basefolder}' 32 | groups: '{target_groups}' 33 | ostype: 'FreeBSD_64' 34 | register: true 35 | 36 | - createmedium: 37 | args: 38 | - "disk" 39 | opts: 40 | filename: "{target_basefolder}{target_groups}/{target_vmname}/{target_vmname}.vdi" 41 | size: 4096 42 | format: "vdi" 43 | 44 | - modifyvm: 45 | args: '{target_vmname}' 46 | opts: 47 | nic1: "bridged" 48 | bridgeadapter1: '{target_bridgeadapter}' 49 | nictype1: "82540EM" # Intel PRO/1000 MT Desktop 50 | macaddress1: "{vmhost_nic_prefix}01" 51 | cableconnected1: "on" 52 | 53 | nic2: "bridged" 54 | bridgeadapter2: '{target_bridgeadapter}' 55 | nictype2: "82540EM" # Intel PRO/1000 MT Desktop 56 | macaddress2: "{vmhost_nic_prefix}02" 57 | cableconnected2: "on" 58 | 59 | memory: "1024" 60 | vram: "16" 61 | cpus: "2" 62 | pae: "off" 63 | accelerate3d: "off" 64 | graphicscontroller: "vmsvga" 65 | monitorcount: "1" 66 | mouse: "ps2" 67 | keyboard: "ps2" 68 | audio: "none" 69 | usb: "off" 70 | clipboard-mode: "disabled" 71 | draganddrop: "disabled" 72 | 73 | boot1: disk 74 | boot2: dvd 75 | boot3: none 76 | boot4: none 77 | 78 | - storagectl: 79 | args: '{target_vmname}' 80 | opts: 81 | add: "sata" 82 | name: "AHCI" 83 | controller: "IntelAhci" 84 | portcount: 2 85 | bootable: "on" 86 | 87 | - storageattach: 88 | args: '{target_vmname}' 89 | opts: 90 | storagectl: "AHCI" 91 | port: 0 92 | type: dvddrive 93 | medium: '{iso_path}/{iso_filename}' 94 | 95 | - storageattach: 96 | args: '{target_vmname}' 97 | opts: 98 | storagectl: "AHCI" 99 | port: 1 100 | type: hdd 101 | medium: '{target_basefolder}{target_groups}/{target_vmname}/{target_vmname}.vdi' 102 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/usr/local/www/fauxapi/admin/logs.php: -------------------------------------------------------------------------------- 1 | "fauxapi") 30 | ); 31 | ?> 32 | 33 |
34 |
35 |

FauxAPI Logs

36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 57 | 60 | 63 | 64 | 65 | 66 | 67 |
52 | 53 | 55 | 56 | 58 | 59 | 61 | 62 |
68 |
69 |
70 |
71 | 72 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/etc/inc/fauxapi/fauxapi_logger.inc: -------------------------------------------------------------------------------- 1 | fauxApiLogger::$log_count_limit) { 99 | array_shift($__fauxapi_logs); 100 | } 101 | return $log; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/usr/local/www/fauxapi/admin/credentials.php: -------------------------------------------------------------------------------- 1 | $ini_section_items) { 40 | if(isset($ini_section_items['secret'])) { 41 | if(!isset($ini_section_items['permit'])) { 42 | $ini_section_items['permit'] = '<none>'; 43 | } 44 | 45 | if(array_key_exists('comment', $ini_section_items)) { 46 | $comment = $ini_section_items['comment']; 47 | } 48 | elseif(array_key_exists('owner', $ini_section_items)) { 49 | // legacy 50 | $comment = $ini_section_items['owner']; 51 | } 52 | else { 53 | $comment = '-'; 54 | } 55 | 56 | $credentials[] = array( 57 | 'apikey' => $ini_section, 58 | 'permits' => explode(',',str_replace(' ', '', $ini_section_items['permit'])), 59 | 'comment' => $comment 60 | ); 61 | } 62 | } 63 | return $credentials; 64 | } 65 | 66 | ?> 67 | 68 |
69 |
70 |

FauxAPI credentials

71 |
72 |
73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | '; 87 | print ''; 88 | print ''; 89 | print ''; 94 | print ''; 95 | print ''; 96 | } 97 | ?> 98 | 99 |
keysecretpermitscomment
'.$credential['apikey'].'
[hidden]
'; 90 | foreach($credential['permits'] as $permit){ 91 | print $permit.'
'; 92 | } 93 | print '
'.$credential['comment'].'
100 |
101 |
102 |
103 | 104 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/etc/inc/fauxapi/fauxapi_utils.inc: -------------------------------------------------------------------------------- 1 | $__recurse_limit) { 42 | throw new \Exception('FATAL: recusion limit reached in sanitize()'); 43 | } 44 | 45 | $allow = null; 46 | if (!empty($allowed)) { 47 | foreach ($allowed as $value) { 48 | $allow .= "\\$value"; 49 | } 50 | } 51 | 52 | if (is_array($input)) { 53 | $cleaned = array(); 54 | foreach ($input as $key => $clean) { 55 | $cleaned[$key] = fauxApiUtils::sanitize($clean, $allowed, $__recurse_count + 1); 56 | } 57 | } else { 58 | $cleaned = preg_replace("/[^{$allow}a-zA-Z0-9]/", '', $input); 59 | } 60 | return $cleaned; 61 | } 62 | 63 | /** 64 | * get_client_ipaddr() 65 | * @return string 66 | */ 67 | public static function get_client_ipaddr() { 68 | $ipaddress = NULL; 69 | 70 | if(getenv('HTTP_CLIENT_IP')) { 71 | $ipaddress = getenv('HTTP_CLIENT_IP'); 72 | } 73 | elseif(getenv('HTTP_X_FORWARDED_FOR')){ 74 | $ipaddress = getenv('HTTP_X_FORWARDED_FOR'); 75 | } 76 | elseif(getenv('HTTP_X_FORWARDED')) { 77 | $ipaddress = getenv('HTTP_X_FORWARDED'); 78 | } 79 | elseif(getenv('HTTP_FORWARDED_FOR')) { 80 | $ipaddress = getenv('HTTP_FORWARDED_FOR'); 81 | } 82 | elseif(getenv('HTTP_FORWARDED')) { 83 | $ipaddress = getenv('HTTP_FORWARDED'); 84 | } 85 | elseif(getenv('REMOTE_ADDR')) { 86 | $ipaddress = getenv('REMOTE_ADDR'); 87 | } else { 88 | $ipaddress = 'UNKNOWN'; 89 | } 90 | return $ipaddress; 91 | } 92 | 93 | /** 94 | * get_request_scheme() 95 | * @return string 96 | */ 97 | public static function get_request_scheme() { 98 | $scheme = NULL; 99 | 100 | if(getenv('REQUEST_SCHEME')) { 101 | $scheme = strtolower(getenv('REQUEST_SCHEME')); 102 | } 103 | elseif(getenv('HTTPS') && strtolower(getenv('HTTPS')) !== 'off') { 104 | $scheme = 'https'; 105 | } 106 | elseif(getenv('SERVER_PORT') && (int)getenv('SERVER_PORT') === 443) { 107 | $scheme = 'https'; 108 | } else { 109 | $scheme = 'http'; 110 | } 111 | return $scheme; 112 | } 113 | 114 | /** 115 | * get_request_port() 116 | * @return int 117 | */ 118 | public static function get_request_port() { 119 | return (int) getenv('SERVER_PORT'); 120 | } 121 | 122 | /** 123 | * get_request_ipaddr() 124 | * @return string 125 | */ 126 | public static function get_request_ipaddr() { 127 | $ipaddress = NULL; 128 | 129 | if(getenv('SERVER_ADDR')) { 130 | $ipaddress = getenv('SERVER_ADDR'); 131 | } else { 132 | $ipaddress = 'UNKNOWN'; 133 | } 134 | return $ipaddress; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/etc/inc/fauxapi/fauxapi_auth.inc: -------------------------------------------------------------------------------- 1 | 'abcdefghijklmnopqrstuvwxyz0123456789abcd', 31 | 'PFFAexample02' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCD', 32 | ); 33 | 34 | /** 35 | * is_authenticated() 36 | * 37 | * @return boolean 38 | */ 39 | public function is_authenticated() { 40 | fauxApiLogger::debug(__METHOD__); 41 | 42 | if (!isset($_SERVER['HTTP_FAUXAPI_AUTH'])) { 43 | fauxApiLogger::error('request missing FAUXAPI_AUTH header value'); 44 | return FALSE; 45 | } 46 | 47 | // make sure the AUTH is well formed and has expected input 48 | $elements = explode(':', fauxApiUtils::sanitize($_SERVER['HTTP_FAUXAPI_AUTH'], array(':'))); 49 | if (4 !== count($elements)) { 50 | fauxApiLogger::error('unexpected number of FAUXAPI_AUTH elements supplied', array( 51 | $elements 52 | )); 53 | return FALSE; 54 | } 55 | 56 | $auth = array( 57 | 'apikey' => $elements[0], 58 | 'timestamp' => $elements[1], 59 | 'nonce' => $elements[2], 60 | 'hash' => $elements[3], 61 | ); 62 | 63 | if(!$this->load_credentials($this->api_credentials_file, $auth['apikey'])) { 64 | return FALSE; 65 | } 66 | 67 | // make sure demo credentials are not in use 68 | foreach($this->api_demo_credentials as $apidemo_key => $apidemo_secret) { 69 | if($apidemo_key === $auth['apikey'] || $apidemo_secret === $this->credentials[FAUXAPI_CALLID]['secret']) { 70 | fauxApiLogger::error('demo api credential value in use, these MUST be changed!'); 71 | return FALSE; 72 | } 73 | } 74 | 75 | // confirm the timestamp is valid 76 | $caller_ts = strtotime($auth['timestamp']); 77 | $system_ts = time(); 78 | 79 | if ($system_ts < ($caller_ts - $this->api_timestamp_delta_max) || 80 | $system_ts > ($caller_ts + $this->api_timestamp_delta_max)) { 81 | fauxApiLogger::error('timestamp provided is out-of-bounds', array( 82 | 'caller_time' => gmdate('Ymd\ZHis', $caller_ts), 83 | 'system_time' => gmdate('Ymd\ZHis', $system_ts) 84 | )); 85 | return FALSE; 86 | } 87 | 88 | // TODO - nonce 89 | // these nonce values really should be checked to ensure requests are not 90 | // replayed by a third party, should be easy enough to do with files 91 | // contained in a /tmp/fauxapi/nonce path for example 92 | 93 | // confirm the nonce is valid 94 | if (strlen($auth['nonce']) < 8 || strlen($auth['nonce']) > 40) { 95 | fauxApiLogger::error('length of nonce value is out-of-bounds'); 96 | return FALSE; 97 | } 98 | 99 | // confirm the api credentials now in use meet the rules:- 100 | // - NB1: refer to fauxApiUtils::sanitize() above, hence alpha numeric only 101 | // - NB2: MUST start with the prefix PFFA (pfSense Faux API) 102 | // - NB3: MUST be >= 12 chars AND <= 40 chars in total length 103 | // - NB4: MUST be >= 40 chars AND <= 128 chars in length 104 | if('PFFA' !== substr($auth['apikey'], 0, 4) || strlen($auth['apikey']) < 12 || strlen($auth['apikey']) > 40) { 105 | fauxApiLogger::error('apikey is out-of-bounds, refer to documentation'); 106 | return FALSE; 107 | } 108 | if(strlen($this->credentials[FAUXAPI_CALLID]['secret']) < 40 || strlen($this->credentials[FAUXAPI_CALLID]['secret']) > 128) { 109 | fauxApiLogger::error('apisecret is out-of-bounds, refer to documentation'); 110 | return FALSE; 111 | } 112 | 113 | // confirm the hash is valid 114 | $auth_hash_input = $this->credentials[FAUXAPI_CALLID]['secret'] . $auth['timestamp'] . $auth['nonce']; 115 | if (hash('sha256', $auth_hash_input) !== $auth['hash']) { 116 | fauxApiLogger::error('invalid hash value provided'); 117 | return FALSE; 118 | } 119 | 120 | define('FAUXAPI_APIKEY', $auth['apikey']); 121 | fauxApiLogger::debug('valid auth for call', array( 122 | 'apikey' => FAUXAPI_APIKEY, 123 | 'callid' => FAUXAPI_CALLID, 124 | 'client_ip' => fauxApiUtils::get_client_ipaddr() 125 | )); 126 | return TRUE; 127 | } 128 | 129 | /** 130 | * is_authorized() 131 | * 132 | * @param string $action 133 | * @return bool 134 | */ 135 | public function is_authorized($action) { 136 | fauxApiLogger::debug(__METHOD__); 137 | 138 | if (!array_key_exists(FAUXAPI_CALLID, $this->credentials)) { 139 | fauxApiLogger::error('credentials for this action have not been established'); 140 | return FALSE; 141 | } 142 | 143 | $permits = explode(',',str_replace(' ', '', $this->credentials[FAUXAPI_CALLID]['permit'])); 144 | foreach($permits as $permit){ 145 | if(fnmatch($permit, $action)) { 146 | fauxApiLogger::debug('permit allows action', array( 147 | 'action' => $action, 148 | 'permit' => $permit, 149 | 'permits' => $permits, 150 | )); 151 | return TRUE; 152 | } 153 | } 154 | fauxApiLogger::debug('permits do NOT allow action!', array( 155 | 'action' => $action, 156 | 'permits' => $permits, 157 | )); 158 | return FALSE; 159 | } 160 | 161 | /** 162 | * load_credentials() 163 | * 164 | * @param string $filename 165 | * @return mixed 166 | */ 167 | private function load_credentials($filename, $apikey) { 168 | fauxApiLogger::debug(__METHOD__); 169 | 170 | if(!is_file($filename)) { 171 | fauxApiLogger::error('unable to find fauxapi credentials file', array( 172 | 'filename' => $filename 173 | )); 174 | return FALSE; 175 | } 176 | 177 | $ini_credentials = parse_ini_file($filename, TRUE); 178 | 179 | $credentials = array(); 180 | foreach($ini_credentials as $key => $ini) { 181 | if(isset($ini['secret']) && !array_key_exists($key, $this->api_demo_credentials)) { 182 | $credentials[$key] = array( 183 | 'secret' => $ini['secret'], 184 | 'permit' => array_key_exists('permit', $ini) ? $ini['permit'] : '' 185 | ); 186 | } 187 | } 188 | 189 | if (array_key_exists($apikey, $credentials)) { 190 | $this->credentials[FAUXAPI_CALLID] = $credentials[$apikey]; 191 | return TRUE; 192 | } 193 | 194 | fauxApiLogger::error('apikey not defined in credential file', array( 195 | 'apikey' => $apikey, 196 | 'filename' => $filename 197 | )); 198 | return FALSE; 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/etc/inc/fauxapi/fauxapi.inc: -------------------------------------------------------------------------------- 1 | debug = $debug; 43 | $this->Auth = new fauxApiAuth(); 44 | } 45 | 46 | /** 47 | * __call 48 | * @param string $user_action 49 | * @param array $user_args 50 | * @throws Exception 51 | */ 52 | public function __call($user_action, $call_args) { 53 | 54 | // info-level log only if a non GET request 55 | if('GET' !== getenv('REQUEST_METHOD')) { 56 | $logger_function = 'info'; 57 | } else { 58 | $logger_function = 'debug'; 59 | } 60 | 61 | fauxApiLogger::$logger_function(__METHOD__, array( 62 | 'user_action' => $user_action, 63 | 'callid' => FAUXAPI_CALLID, 64 | 'client_ip' => fauxApiUtils::get_client_ipaddr() 65 | )); 66 | 67 | $Actions = new fauxApiActions(); 68 | 69 | // check this user action request 70 | $checked = $this->__check_user_action_call($Actions, $user_action, $call_args[0], $call_args[1]); 71 | 72 | // create a $response object to work with below 73 | $response = (object) array( 74 | 'callid' => FAUXAPI_CALLID, 75 | 'http_code' => NULL, 76 | 'action' => NULL, 77 | 'message' => NULL, 78 | 'data' => NULL, 79 | 'logs' => NULL); 80 | 81 | if(!empty($checked->http_code)) { 82 | $response->message = $checked->message; 83 | $response->http_code = $checked->http_code; 84 | 85 | } else { 86 | $Actions->response = &$response; 87 | $Actions->action_input_data = &$checked->data; 88 | 89 | $response->action = $checked_action = $checked->action; 90 | $Actions->$checked_action($checked->args); 91 | 92 | if(empty($response->http_code)) { 93 | $response->http_code = 500; 94 | $response->message = 'unexpected call response status'; 95 | } 96 | } 97 | 98 | if (200 !== $response->http_code || TRUE === $this->debug) { 99 | if(empty($response->action)) { 100 | // feels prudent to provide limited return data in this case, also feels messy 101 | unset($response->logs); 102 | unset($response->action); 103 | } else { 104 | $response->logs = fauxApiLogger::get_logs($this->debug); 105 | } 106 | } else { 107 | unset($response->logs); 108 | } 109 | 110 | if(is_null($response->data)) { 111 | unset($response->data); 112 | } 113 | 114 | return $response; 115 | } 116 | 117 | /** 118 | * __check_user_action_call() 119 | * 120 | * @param string $user_action 121 | * @param array $user_args 122 | * @param string $user_data 123 | * @return object 124 | */ 125 | private function __check_user_action_call($Actions, $user_action, $user_args, $user_data) { 126 | fauxApiLogger::debug(__METHOD__); 127 | 128 | // create a $response object to work with below 129 | $checked = (object) array( 130 | 'http_code' => NULL, 131 | 'message' => NULL, 132 | 'action'=> NULL, 133 | 'args' => array(), 134 | 'data' => NULL 135 | ); 136 | 137 | // confirm request is authenticated before we get too involved with it 138 | if (TRUE !== $this->Auth->is_authenticated()) { 139 | $checked->http_code = 401; 140 | $checked->message = 'authentication failed'; 141 | fauxApiLogger::error($checked->message); 142 | return $checked; 143 | } 144 | 145 | // confirm the user action data does not need to be scrubbed 146 | $permitted = array('_'); 147 | $checked->action = fauxApiUtils::sanitize((string)$user_action, $permitted); 148 | if($user_action !== $checked->action) { 149 | $checked->http_code = 400; 150 | $checked->message = 'user action name contains non-permitted values'; 151 | fauxApiLogger::error($checked->message, array( 152 | 'user_action' => $user_action, 153 | 'checked_action' => $checked->action 154 | )); 155 | return $checked; 156 | } 157 | 158 | // confirm the method being called actually exists 159 | if (!method_exists($Actions, $checked->action)) { 160 | $checked->http_code = 404; 161 | $checked->message = 'api action is not defined'; 162 | fauxApiLogger::error($checked->message, array( 163 | 'action' => $checked->action 164 | )); 165 | return $checked; 166 | } 167 | 168 | // check the request is authorized to be performed 169 | if (TRUE !== $this->Auth->is_authorized($checked->action)) { 170 | $checked->http_code = 401; 171 | $checked->message = 'action authorization failed'; 172 | fauxApiLogger::error($checked->message, array( 173 | 'action' => $checked->action 174 | )); 175 | return $checked; 176 | } 177 | 178 | // confirm any user_data provided is valid JSON 179 | if(!empty($user_data)) { 180 | $checked->data = @json_decode($user_data, TRUE); 181 | $json_last_error = json_last_error(); 182 | if(NULL === $checked->data && $json_last_error !== JSON_ERROR_NONE) { 183 | $checked->http_code = 400; 184 | $checked->message = 'user action data is not valid JSON'; 185 | fauxApiLogger::error($checked->message, array( 186 | 'json_last_error' => $json_last_error 187 | )); 188 | return $checked; 189 | } 190 | } 191 | 192 | // sanity check and clean up the user args provided direct from $_GET 193 | foreach($user_args as $arg_key=>$arg_value) { 194 | $permitted = array(' ','_','-','.','/'); 195 | if($arg_key !== fauxApiUtils::sanitize($arg_key, $permitted) || $arg_value !== fauxApiUtils::sanitize($arg_value, $permitted)) { 196 | $checked->http_code = 400; 197 | $checked->message = 'user action arguments contain non-permitted values'; 198 | fauxApiLogger::error($checked->message, array( 199 | 'arg_key' => $arg_key, 200 | 'arg_value' => $arg_value, 201 | )); 202 | return $checked; 203 | } 204 | if($arg_key !== 'action') { 205 | if('true'=== strtolower($arg_value)) { 206 | $arg_value = TRUE; 207 | } 208 | if('false' === strtolower($arg_value)) { 209 | $arg_value = FALSE; 210 | } 211 | if('__debug' === $arg_key) { 212 | $this->debug = $arg_value; 213 | } 214 | else { 215 | $checked->args[$arg_key] = $arg_value; 216 | } 217 | } 218 | } 219 | 220 | fauxApiLogger::debug(__METHOD__.'() checks all passed'); 221 | return $checked; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016-2018 Nicholas de Jong 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/etc/inc/fauxapi/fauxapi_actions.inc: -------------------------------------------------------------------------------- 1 | PfsenseInterface = new fauxApiPfsenseInterface(); 36 | } 37 | 38 | /** 39 | * config_get() 40 | * 41 | * @param array $__args 42 | * @return boolean 43 | */ 44 | public function config_get($__args) { 45 | fauxApiLogger::debug(__METHOD__); 46 | 47 | $config_file = NULL; 48 | extract($__args, EXTR_IF_EXISTS); 49 | 50 | if(empty($config_file)) { 51 | $config_file = $this->PfsenseInterface->config_default_filename; 52 | } 53 | 54 | $config = $this->PfsenseInterface->config_load($config_file); 55 | 56 | if (empty($config)) { 57 | $this->response->http_code = 500; 58 | $this->response->message = 'unable to load config file'; 59 | $this->response->data = array( 60 | 'config_file' => $config_file); 61 | return FALSE; 62 | } 63 | $this->response->http_code = 200; 64 | $this->response->message = 'ok'; 65 | $this->response->data = array( 66 | 'config_file' => $config_file, 67 | 'config' => $config); 68 | return TRUE; 69 | } 70 | 71 | /** 72 | * config_set() 73 | * 74 | * @param array $__args 75 | * @return boolean 76 | */ 77 | public function config_set($__args) { 78 | fauxApiLogger::debug(__METHOD__); 79 | 80 | $do_backup = TRUE; 81 | $do_reload = TRUE; 82 | extract($__args, EXTR_IF_EXISTS); 83 | 84 | // save this new config 85 | if (!$this->PfsenseInterface->config_save($this->action_input_data, $do_backup, $do_reload)) { 86 | $this->response->http_code = 500; 87 | $this->response->message = 'failed to set new config'; 88 | return FALSE; 89 | } 90 | $this->response->http_code = 200; 91 | $this->response->message = 'ok'; 92 | $this->response->data = array( 93 | 'do_backup' => $do_backup, 94 | 'do_reload' => $do_reload, 95 | 'previous_config_file' => $this->PfsenseInterface->backup_config_filename[FAUXAPI_CALLID]); 96 | return TRUE; 97 | } 98 | 99 | /** 100 | * config_patch() 101 | * 102 | * @param array $__args 103 | * @return boolean 104 | */ 105 | public function config_patch($__args) { 106 | fauxApiLogger::debug(__METHOD__); 107 | 108 | $do_backup = TRUE; 109 | $do_reload = TRUE; 110 | extract($__args, EXTR_IF_EXISTS); 111 | 112 | // patch the config data 113 | if (!$this->PfsenseInterface->config_patch($this->action_input_data, $do_backup, $do_reload)) { 114 | $this->response->http_code = 500; 115 | $this->response->message = 'failed to patch config data'; 116 | return FALSE; 117 | } 118 | 119 | $this->response->http_code = 200; 120 | $this->response->message = 'ok'; 121 | $this->response->data = array( 122 | 'do_backup' => $do_backup, 123 | 'do_reload' => $do_reload, 124 | 'previous_config_file' => $this->PfsenseInterface->backup_config_filename[FAUXAPI_CALLID]); 125 | return TRUE; 126 | } 127 | 128 | /** 129 | * config_item_get() 130 | * 131 | * @param array $__args 132 | * @return boolean 133 | */ 134 | public function config_item_get($__args) { 135 | fauxApiLogger::debug(__METHOD__); 136 | 137 | $item = NULL; 138 | extract($__args, EXTR_IF_EXISTS); 139 | 140 | $value = $this->PfsenseInterface->config_item_get($item); 141 | 142 | if (FALSE === $value) { 143 | $this->response->http_code = 500; 144 | $this->response->message = 'unable to get config item value'; 145 | $this->response->data = array( 146 | 'item' => $item); 147 | return FALSE; 148 | } 149 | $this->response->http_code = 200; 150 | $this->response->message = 'ok'; 151 | $this->response->data = array( 152 | 'item' => $item, 153 | 'value' => $value); 154 | return TRUE; 155 | } 156 | 157 | /** 158 | * config_item_set() 159 | * 160 | * @param array $__args 161 | * @return boolean 162 | */ 163 | public function config_item_set($__args) { 164 | fauxApiLogger::debug(__METHOD__); 165 | 166 | $item = NULL; 167 | $do_backup = TRUE; 168 | $do_reload = TRUE; 169 | $do_insert = FALSE; 170 | extract($__args, EXTR_IF_EXISTS); 171 | 172 | if (!$this->PfsenseInterface->config_item_set($item, $this->action_input_data, $do_insert, $do_backup, $do_reload)) { 173 | $this->response->http_code = 500; 174 | $this->response->message = 'failed to set config item value'; 175 | $this->response->data = array( 176 | 'item' => $item, 177 | 'value' => $value); 178 | return FALSE; 179 | } 180 | 181 | $this->response->http_code = 200; 182 | $this->response->message = 'ok'; 183 | $this->response->data = array( 184 | 'item' => $item, 185 | 'value' => $value, 186 | 'do_insert' => $do_insert, 187 | 'do_backup' => $do_backup, 188 | 'do_reload' => $do_reload, 189 | 'previous_config_file' => $this->PfsenseInterface->backup_config_filename[FAUXAPI_CALLID]); 190 | return TRUE; 191 | } 192 | 193 | /** 194 | * config_reload() 195 | * 196 | * @return boolean 197 | */ 198 | public function config_reload() { 199 | fauxApiLogger::debug(__METHOD__); 200 | 201 | if (!$this->PfsenseInterface->system_load_config()) { 202 | $this->response->http_code = 500; 203 | $this->response->message = 'unable to reload current config'; 204 | return FALSE; 205 | } 206 | $this->response->http_code = 200; 207 | $this->response->message = 'ok'; 208 | return TRUE; 209 | } 210 | 211 | /** 212 | * config_backup() 213 | * 214 | * @return boolean 215 | */ 216 | public function config_backup() { 217 | fauxApiLogger::debug(__METHOD__); 218 | 219 | $backup_config_file = $this->PfsenseInterface->config_backup(); 220 | 221 | if(!is_file($backup_config_file)) { 222 | $this->response->http_code = 500; 223 | $this->response->message = 'unable to create config backup'; 224 | $this->response->data = array( 225 | 'backup_config_file' => $backup_config_file); 226 | return FALSE; 227 | } 228 | $this->response->http_code = 200; 229 | $this->response->message = 'ok'; 230 | $this->response->data = array( 231 | 'backup_config_file' => $backup_config_file); 232 | return TRUE; 233 | } 234 | 235 | /** 236 | * config_backup_list() 237 | * 238 | * @return boolean 239 | */ 240 | public function config_backup_list() { 241 | fauxApiLogger::debug(__METHOD__); 242 | 243 | $backup_files = $this->PfsenseInterface->config_backup_list(); 244 | 245 | if (!is_array($backup_files)) { 246 | $this->response->http_code = 500; 247 | $this->response->message = 'unable to obtain list of backup files'; 248 | return FALSE; 249 | } 250 | $this->response->http_code = 200; 251 | $this->response->message = 'ok'; 252 | $this->response->data = array( 253 | 'backup_files' => $backup_files); 254 | return TRUE; 255 | } 256 | 257 | /** 258 | * config_restore() 259 | * 260 | * @param array $__args 261 | * @return boolean 262 | */ 263 | public function config_restore($__args) { 264 | fauxApiLogger::debug(__METHOD__); 265 | 266 | $config_file = NULL; 267 | extract($__args, EXTR_IF_EXISTS); 268 | 269 | if (!$this->PfsenseInterface->system_load_config($config_file)) { 270 | $this->response->http_code = 500; 271 | $this->response->message = 'unable to restore backup config'; 272 | $this->response->data = array( 273 | 'config_file' => $config_file); 274 | return FALSE; 275 | } 276 | $this->response->http_code = 200; 277 | $this->response->message = 'ok'; 278 | $this->response->data = array( 279 | 'config_file' => $config_file); 280 | return TRUE; 281 | } 282 | 283 | /** 284 | * send_event 285 | * 286 | * @return boolean 287 | */ 288 | public function send_event() { 289 | fauxApiLogger::debug(__METHOD__); 290 | 291 | if(!is_array($this->action_input_data) || !isset($this->action_input_data[0])) { 292 | $this->response->http_code = 400; 293 | $this->response->message = 'incorrectly formatted request data'; 294 | return FALSE; 295 | } 296 | 297 | $command = $this->action_input_data[0]; 298 | 299 | if (!$this->PfsenseInterface->send_event($command)) { 300 | $this->response->http_code = 400; 301 | $this->response->message = 'unable to send_event()'; 302 | $this->response->data = array( 303 | 'command' => $command); 304 | return FALSE; 305 | } 306 | $this->response->http_code = 200; 307 | $this->response->message = 'ok'; 308 | return TRUE; 309 | } 310 | 311 | /** 312 | * system_reboot() 313 | * 314 | * @return boolean 315 | */ 316 | public function system_reboot() { 317 | fauxApiLogger::debug(__METHOD__); 318 | 319 | if (!$this->PfsenseInterface->system_reboot()) { 320 | $this->response->http_code = 500; 321 | $this->response->message = 'unable to issue system reboot'; 322 | return FALSE; 323 | } 324 | $this->response->http_code = 200; 325 | $this->response->message = 'ok'; 326 | return TRUE; 327 | } 328 | 329 | /** 330 | * system_stats() 331 | * 332 | * @return boolean 333 | */ 334 | public function system_stats() { 335 | fauxApiLogger::debug(__METHOD__); 336 | 337 | $stats = $this->PfsenseInterface->system_stats(); 338 | 339 | if (empty($stats)) { 340 | $this->response->http_code = 500; 341 | $this->response->message = 'unable to get system stats'; 342 | return FALSE; 343 | } 344 | $this->response->http_code = 200; 345 | $this->response->message = 'ok'; 346 | $this->response->data = array( 347 | 'stats' => $stats 348 | ); 349 | return TRUE; 350 | } 351 | 352 | /** 353 | * system_stats() 354 | * 355 | * @return boolean 356 | */ 357 | public function system_info() { 358 | fauxApiLogger::debug(__METHOD__); 359 | 360 | $info = $this->PfsenseInterface->system_info(); 361 | 362 | if (empty($info)) { 363 | $this->response->http_code = 500; 364 | $this->response->message = 'unable to get system info'; 365 | return FALSE; 366 | } 367 | $this->response->http_code = 200; 368 | $this->response->message = 'ok'; 369 | $this->response->data = array( 370 | 'info' => $info 371 | ); 372 | return TRUE; 373 | } 374 | 375 | /** 376 | * interface_stats() 377 | * 378 | * @return boolean 379 | */ 380 | public function interface_stats($__args) { 381 | fauxApiLogger::debug(__METHOD__); 382 | 383 | $interface = NULL; 384 | extract($__args, EXTR_IF_EXISTS); 385 | 386 | $stats = $this->PfsenseInterface->interface_stats($interface); 387 | 388 | if (empty($stats)) { 389 | $this->response->http_code = 500; 390 | $this->response->message = 'unable to get interface stats'; 391 | return FALSE; 392 | } 393 | $this->response->http_code = 200; 394 | $this->response->message = 'ok'; 395 | $this->response->data = array( 396 | 'stats' => $stats 397 | ); 398 | return TRUE; 399 | } 400 | 401 | /** 402 | * rule_get() 403 | * 404 | * @param array $__args 405 | * @return boolean 406 | */ 407 | public function rule_get($__args) { 408 | fauxApiLogger::debug(__METHOD__); 409 | 410 | $rule_number = NULL; 411 | extract($__args, EXTR_IF_EXISTS); 412 | 413 | $rules = $this->PfsenseInterface->rule_get((int)$rule_number); 414 | 415 | if (empty($rules)) { 416 | $this->response->http_code = 500; 417 | $this->response->message = 'unable to get rule(s)'; 418 | return FALSE; 419 | } 420 | $this->response->http_code = 200; 421 | $this->response->message = 'ok'; 422 | $this->response->data = array( 423 | 'rules' => $rules 424 | ); 425 | return TRUE; 426 | } 427 | 428 | /** 429 | * alias_update_urltables() 430 | * 431 | * @param array $__args 432 | * @return boolean 433 | */ 434 | public function alias_update_urltables($__args) { 435 | fauxApiLogger::debug(__METHOD__); 436 | 437 | $table = NULL; 438 | extract($__args, EXTR_IF_EXISTS); 439 | 440 | $updates = $this->PfsenseInterface->alias_update_urltables($table); 441 | 442 | $this->response->http_code = 200; 443 | $this->response->message = 'ok'; 444 | $this->response->data = array( 445 | 'updates' => $updates 446 | ); 447 | return TRUE; 448 | } 449 | 450 | /** 451 | * gateway_status() 452 | * 453 | * @return boolean 454 | */ 455 | public function gateway_status() { 456 | fauxApiLogger::debug(__METHOD__); 457 | 458 | $gateway_status = $this->PfsenseInterface->gateway_status(); 459 | 460 | if (empty($gateway_status)) { 461 | $this->response->http_code = 500; 462 | $this->response->message = 'unable to get gateway status'; 463 | return FALSE; 464 | } 465 | $this->response->http_code = 200; 466 | $this->response->message = 'ok'; 467 | $this->response->data = array( 468 | 'gateway_status' => $gateway_status 469 | ); 470 | return TRUE; 471 | } 472 | 473 | /** 474 | * function_call() 475 | * 476 | * @return boolean 477 | */ 478 | public function function_call() { 479 | fauxApiLogger::debug(__METHOD__); 480 | 481 | if(!is_array($this->action_input_data)) { 482 | $this->response->http_code = 400; 483 | $this->response->message = 'incorrectly formatted request data'; 484 | return FALSE; 485 | } 486 | 487 | if(!isset($this->action_input_data['function'])) { 488 | $this->response->http_code = 400; 489 | $this->response->message = 'missing function name parameter'; 490 | return FALSE; 491 | } 492 | 493 | $function = $this->action_input_data['function']; 494 | if(!is_string($function)) { 495 | $this->response->http_code = 400; 496 | $this->response->message = 'function is not a string value'; 497 | return FALSE; 498 | } 499 | 500 | $args = array(); 501 | if(isset($this->action_input_data['args'])) { 502 | $args = $this->action_input_data['args']; 503 | if(is_string($args)) { 504 | $args = array($args); 505 | } 506 | } 507 | 508 | $includes = array(); 509 | if(isset($this->action_input_data['includes'])) { 510 | $includes = $this->action_input_data['includes']; 511 | if(is_string($includes)) { 512 | $includes = array($includes); 513 | } 514 | } 515 | 516 | try { 517 | $return = $this->PfsenseInterface->function_call( 518 | $function, 519 | $args, 520 | $includes 521 | ); 522 | } catch (\Exception $e) { 523 | $this->response->http_code = 500; 524 | $this->response->message = 'problem calling function'; 525 | $this->response->error = array('error' => $e); 526 | return FALSE; 527 | } 528 | 529 | $this->response->http_code = 200; 530 | $this->response->message = 'ok'; 531 | $this->response->data = array( 532 | 'return' => $return 533 | ); 534 | return TRUE; 535 | } 536 | } 537 | -------------------------------------------------------------------------------- /pfSense-pkg-FauxAPI/files/etc/inc/fauxapi/fauxapi_pfsense_interface.inc: -------------------------------------------------------------------------------- 1 | $type 60 | )); 61 | 62 | if('pfsense' === $type) { 63 | ## NB: config filename must be parseable by pfsense cleanup_backupcache() 64 | $filename = 'config-'. time() .'.xml'; 65 | $path = $this->config_backup_path; 66 | } 67 | elseif('fauxapi' == $type) { 68 | $filename = 'config-' . time() . '-' . FAUXAPI_APIKEY . '-' . FAUXAPI_CALLID .'.xml'; 69 | $path = $this->config_fauxapi_backup_path; 70 | } 71 | else { 72 | throw new \Exception('unsupported $type requested'); 73 | } 74 | 75 | return $path . '/' . $filename; 76 | } 77 | 78 | /** 79 | * config_load() 80 | * 81 | * @param string $config_file 82 | * @return array 83 | */ 84 | public function config_load($config_file, $__do_safe_check=TRUE) { 85 | fauxApiLogger::debug(__METHOD__, array( 86 | 'config_file' => $config_file 87 | )); 88 | 89 | if($__do_safe_check) { 90 | if(strpos($config_file, $this->config_base_path) !== 0 || strpos($config_file, '..') !== FALSE){ 91 | fauxApiLogger::error('attempting to load config file from non-supported path', array( 92 | 'config_file' => $config_file, 93 | )); 94 | return array(); 95 | } 96 | } 97 | 98 | if(!is_file($config_file)) { 99 | fauxApiLogger::error('requested config file can not be found',array( 100 | 'config_file' => $config_file 101 | )); 102 | return array(); 103 | } 104 | 105 | return \parse_xml_config($config_file, $this->config_xml_root); 106 | } 107 | 108 | /** 109 | * config_patch() 110 | * 111 | * @param array $config_patch 112 | * @param boolean $do_backup 113 | * @param boolean $do_reload 114 | * @return boolean 115 | */ 116 | public function config_patch($config_patch, $do_backup=TRUE, $do_reload=TRUE) { 117 | fauxApiLogger::debug(__METHOD__, array( 118 | 'do_backup' => $do_backup, 119 | 'do_reload' => $do_reload 120 | )); 121 | 122 | $config = $this->array_merge_recursive_distinct( 123 | $this->config_load($this->config_default_filename), 124 | $config_patch 125 | ); 126 | 127 | fauxApiLogger::info('config_patch merged with current config, attempting to save'); 128 | return $this->config_save($config, $do_backup, $do_reload); 129 | } 130 | 131 | /** 132 | * array_merge_recursive_distinct() 133 | * 134 | * @param array $array1 135 | * @param array $array2 136 | * @return array 137 | * @note adapted from http://php.net/manual/en/function.array-merge-recursive.php#92195 138 | */ 139 | private function array_merge_recursive_distinct(array &$array1, array &$array2) { 140 | $merged = $array1; 141 | foreach ($array2 as $key => &$value) { 142 | if (is_array($value) && !$this->is_numeric_array($value) && isset($merged[$key]) && is_array($merged[$key])) { 143 | $merged[$key] = $this->array_merge_recursive_distinct($merged[$key], $value); 144 | } else { 145 | $merged[$key] = $value; 146 | } 147 | } 148 | return $merged; 149 | } 150 | 151 | /** 152 | * is_numeric_array() 153 | * 154 | * @param array $array 155 | * @return bool 156 | * @note adapted from https://codereview.stackexchange.com/questions/201/is-numeric-array-is-missing 157 | */ 158 | private function is_numeric_array($array) { 159 | return array_keys($array) === range(0,(count($array)-1)); 160 | } 161 | 162 | /** 163 | * config_item_get() 164 | * 165 | * @param string $item 166 | * @return string 167 | */ 168 | public function config_item_get($item) { 169 | fauxApiLogger::debug(__METHOD__, array( 170 | 'item' => $item 171 | )); 172 | 173 | // not yet implemented !! 174 | fauxApiLogger::error('not yet implemented'); 175 | return FALSE; 176 | } 177 | 178 | /** 179 | * config_item_set() 180 | * 181 | * @param string $item 182 | * @param string $value 183 | * @param boolean $do_insert 184 | * @param boolean $do_backup 185 | * @param boolean $do_reload 186 | * @return boolean 187 | */ 188 | public function config_item_set($item, $value, $do_insert=FALSE, $do_backup=TRUE, $do_reload=TRUE) { 189 | fauxApiLogger::debug(__METHOD__, array( 190 | 'item' => $item, 191 | 'value' => $value, 192 | 'do_insert' => $do_insert, 193 | 'do_backup' => $do_backup, 194 | 'do_reload' => $do_reload 195 | )); 196 | 197 | // not yet implemented !! 198 | fauxApiLogger::error('not yet implemented'); 199 | return FALSE; 200 | } 201 | 202 | /** 203 | * config_save() 204 | * 205 | * @param array $config 206 | * @param boolean $do_backup 207 | * @param boolean $do_reload 208 | * @return boolean 209 | */ 210 | public function config_save($config, $do_backup=TRUE, $do_reload=TRUE) { 211 | fauxApiLogger::debug(__METHOD__, array( 212 | 'do_backup' => $do_backup, 213 | 'do_reload' => $do_reload 214 | )); 215 | 216 | $config_file = $this->config_default_filename; 217 | 218 | if (TRUE === $do_backup) { 219 | $config_backup_file = $this->config_backup($config_file); 220 | if (!is_file($config_backup_file)) { 221 | return FALSE; 222 | } 223 | $this->backup_config_filename[FAUXAPI_CALLID] = $config_backup_file; 224 | } 225 | else { 226 | $this->backup_config_filename[FAUXAPI_CALLID] = FALSE; 227 | } 228 | 229 | $username = 'fauxapi-'.FAUXAPI_APIKEY.'@'.fauxApiUtils::get_client_ipaddr(); 230 | 231 | $config['revision'] = $config_revision = array( 232 | 'time' => time(), 233 | 'description' => $username.': update via fauxapi for callid: '.FAUXAPI_CALLID, 234 | 'username' => $username 235 | ); 236 | 237 | $xml_string = \dump_xml_config($config, $this->config_xml_root); 238 | $config_temp_file = tempnam(sys_get_temp_dir(), 'fauxApi_'); 239 | file_put_contents($config_temp_file, $xml_string); 240 | 241 | fauxApiLogger::debug('attempting to (re)load a temp copy of the config supplied', array( 242 | 'config_temp_file' => $config_temp_file, 243 | )); 244 | $temp_config = $this->config_load($config_temp_file, FALSE); 245 | 246 | // remove the revision data before comparing since we did set it above 247 | $config['revision'] = $temp_config['revision'] = array(); 248 | if ($config !== $temp_config) { 249 | fauxApiLogger::error('saved config does not match config when saved and reloaded'); 250 | return FALSE; 251 | } 252 | $config['revision'] = $config_revision; 253 | 254 | fauxApiLogger::debug('confirmed the config supplied will reload into the same config supplied', array( 255 | 'config_temp_file' => $config_temp_file, 256 | )); 257 | unlink($config_file); 258 | rename($config_temp_file, $config_file); 259 | 260 | if (!is_file($config_file)) { 261 | fauxApiLogger::error('unable to find new config file', $config_file); 262 | return FALSE; 263 | } 264 | 265 | // attempt to reload, if this fails revert to previous backup 266 | if(TRUE === $do_reload) { 267 | if(!$this->system_load_config($config_file)) { 268 | $last_backup_file = $this->config_backup_path .'/'. \discover_last_backup(); 269 | fauxApiLogger::warn('attempting to revert config to last known backup', array( 270 | 'last_backup_file' => $last_backup_file 271 | )); 272 | if(is_file($last_backup_file)) { 273 | if (\config_restore($last_backup_file) !== 0) { // WTF, sucess == 0 ?? 274 | fauxApiLogger::error('unable to reload previous config backup'); 275 | } else { 276 | fauxApiLogger::info('config file reverted to last known backup', array( 277 | 'config_file' => $last_backup_file 278 | )); 279 | } 280 | } else { 281 | fauxApiLogger::error('unable to locate previous backup file to revert'); 282 | } 283 | return FALSE; 284 | } 285 | } 286 | return TRUE; 287 | } 288 | 289 | /** 290 | * config_backup() 291 | * 292 | * @param type $config_file 293 | * @return type 294 | */ 295 | public function config_backup($config_file = NULL, $do_fauxapi_symlink=FALSE) { 296 | fauxApiLogger::debug(__METHOD__, $config_file); 297 | 298 | if (is_null($config_file)) { 299 | $config_file = $this->config_default_filename; 300 | } 301 | 302 | $config_backup_file = $this->get_next_backup_config_filename(); 303 | copy($config_file, $config_backup_file); 304 | 305 | if ($this->config_load($config_file) !== $this->config_load($config_backup_file)) { 306 | fauxApiLogger::error('config backup failed consistency check',array( 307 | 'source_file' => $config_file, 308 | 'backup_file' => $config_backup_file 309 | )); 310 | return NULL; 311 | } 312 | 313 | # register this backup by doing a backup cache cleanup 314 | global $config; 315 | $config = $this->config_load($this->config_default_filename); 316 | \cleanup_backupcache(); 317 | unset($config); 318 | 319 | # create a fauxapi symlink to make these backups easier to identify 320 | if(TRUE === $do_fauxapi_symlink) { 321 | if(!is_dir($this->config_fauxapi_backup_path)) { 322 | mkdir($this->config_fauxapi_backup_path, 0755, TRUE); 323 | } 324 | symlink($config_backup_file, $this->get_next_backup_config_filename('fauxapi')); 325 | } 326 | 327 | return $config_backup_file; 328 | } 329 | 330 | /** 331 | * config_backup_list 332 | */ 333 | public function config_backup_list() { 334 | fauxApiLogger::debug(__METHOD__); 335 | 336 | $backup_cache = unserialize(file_get_contents($this->config_backup_cache)); 337 | 338 | $backup_list = array(); 339 | foreach(array_keys($backup_cache) as $backup_unixtime) { 340 | $backup_filename = $this->config_backup_path.'/config-'.$backup_unixtime.'.xml'; 341 | if(is_file($backup_filename)) { 342 | $data = array( 343 | 'filename' => $backup_filename, 344 | 'timestamp' => date('Ymd\ZHis', (int)$backup_unixtime), 345 | 'description' => $backup_cache[$backup_unixtime]['description'], 346 | 'version' => $backup_cache[$backup_unixtime]['version'], 347 | 'filesize' => $backup_cache[$backup_unixtime]['filesize'], 348 | ); 349 | $backup_list[] = $data; 350 | } 351 | } 352 | return $backup_list; 353 | } 354 | 355 | /** 356 | * system_load_config() 357 | * 358 | * @return bool 359 | * @link https://doc.pfsense.org/index.php/How_can_I_reload_the_config_after_manually_editing_config.xml 360 | */ 361 | public function system_load_config($config_file=NULL) { 362 | fauxApiLogger::debug(__METHOD__, $config_file); 363 | 364 | if(is_null($config_file)) { 365 | $config_file = $this->config_default_filename; 366 | } 367 | 368 | if(strpos($config_file, $this->config_base_path) === FALSE || strpos($config_file, '..') !== FALSE){ 369 | fauxApiLogger::error('attempting to load config file from non-supported path', array( 370 | 'config_file' => $config_file, 371 | )); 372 | return FALSE; 373 | } 374 | 375 | if(!is_file($config_file)) { 376 | fauxApiLogger::error('attempting to load config file that does not exist', array( 377 | 'config_file' => $config_file, 378 | )); 379 | return FALSE; 380 | } 381 | 382 | if($config_file !== $this->config_default_filename) { 383 | copy($config_file, $this->config_default_filename); 384 | } 385 | 386 | if(is_file($this->config_cache_filename)) { 387 | unlink($this->config_cache_filename); 388 | } else { 389 | fauxApiLogger::warn('pfsense config cache file does not exist before reload', array( 390 | 'config_cache_filename' => $this->config_cache_filename 391 | )); 392 | } 393 | 394 | $wait_count_seconds = 0; 395 | while($wait_count_seconds < $this->config_reload_max_wait_secs) { 396 | 397 | // induce the pfsense config.cache to regenerate by requesting index.php 398 | $scheme = fauxApiUtils::get_request_scheme(); 399 | $port = fauxApiUtils::get_request_port(); 400 | $cache_respawn_url = $scheme . '://127.0.0.1:' . $port . '/index.php?__fauxapi_callid='.FAUXAPI_CALLID; 401 | $exec_command = 'curl --silent --insecure "'.addslashes($cache_respawn_url).'" > /dev/null'; 402 | // unable to call file_get_contents() to a URL thus we resort to an exec!! 403 | fauxApiLogger::debug('exec curl', array( 404 | 'exec_command' => $exec_command 405 | )); 406 | exec($exec_command); 407 | 408 | if(is_file($this->config_cache_filename)) { 409 | return TRUE; 410 | } 411 | sleep(1); 412 | $wait_count_seconds++; 413 | } 414 | 415 | fauxApiLogger::error('unable confirm config reload before timeout', array( 416 | 'config_cache_filename' => $this->config_cache_filename, 417 | 'timeout' => $this->config_reload_max_wait_secs 418 | )); 419 | 420 | return FALSE; 421 | } 422 | 423 | /** 424 | * system_reboot() 425 | * 426 | * @return bool 427 | */ 428 | public function system_reboot() { 429 | fauxApiLogger::debug(__METHOD__); 430 | 431 | ignore_user_abort(TRUE); 432 | 433 | ob_start(); 434 | \system_reboot(); 435 | ob_end_clean(); 436 | 437 | return TRUE; 438 | } 439 | 440 | /** 441 | * send_event() 442 | * 443 | * @param string $command 444 | */ 445 | public function send_event($command) { 446 | fauxApiLogger::debug(__METHOD__, $command); 447 | 448 | // 449 | // NB: quick oneliner to catch the commands that pfSense ordinarily sends to send_event() 450 | // grep -r 'send_event(' * | grep -v 'function ' | grep -v 'retval' | cut -d':' -f2 | sed 's/^[\t ]*//g' | sort | uniq 451 | // 452 | // send_event("filter reload"); 453 | // send_event("filter sync"); 454 | // send_event("interface all reload"); 455 | // send_event("interface newip {$iface}"); 456 | // send_event("interface reconfigure {$interface}"); 457 | // send_event("interface reconfigure {$reloadif}"); 458 | // send_event("service reload all"); 459 | // send_event("service reload dyndnsall"); 460 | // send_event("service reload dyndns {$interface}"); 461 | // send_event("service reload ipsecdns"); 462 | // send_event("service reload packages"); 463 | // send_event("service reload sshd"); 464 | // send_event("service restart packages"); 465 | // send_event("service restart sshd"); 466 | // send_event("service restart webgui"); 467 | // send_event("service sync alias {$name}"); 468 | // send_event("service sync vouchers"); 469 | // 470 | // Is this checking a help or a hinderance actually? - NdJ 471 | // 472 | 473 | $valid = array( 474 | 'filter' => array('reload', 'sync'), 475 | 'interface' => array('all', 'newip', 'reconfigure'), 476 | 'service' => array('reload', 'restart', 'sync'), 477 | ); 478 | 479 | $command_parts = explode(' ', (string)$command); 480 | 481 | # check part #1 482 | if(!isset($command_parts[0]) || !in_array($command_parts[0], array_keys($valid))) { 483 | fauxApiLogger::error('supplied command command not listed in valid send_event() set', $command); 484 | return FALSE; 485 | } 486 | 487 | # check part #2 488 | if(!isset($command_parts[1]) || !in_array($command_parts[1], $valid[$command_parts[0]])) { 489 | fauxApiLogger::error('supplied command command not listed in valid send_event() set', $command); 490 | return FALSE; 491 | } 492 | 493 | \send_event($command); 494 | return TRUE; 495 | } 496 | 497 | /** 498 | * system_info 499 | * 500 | * @return array 501 | */ 502 | public function system_info() { 503 | global $g; 504 | fauxApiLogger::debug(__METHOD__); 505 | $info['sys'] = array ( 506 | 'platform'=>system_identify_specific_platform(), 507 | ); 508 | if (function_exists('system_get_serial')) { 509 | $info['sys']['serial_no'] = system_get_serial(); 510 | } else { 511 | $info['sys']['serial_no'] = NULL; 512 | } 513 | if (function_exists('system_get_uniqueid')) { 514 | $info['sys']['device_id'] = system_get_uniqueid(); 515 | } else { 516 | $info['sys']['device_id'] = NULL; 517 | } 518 | 519 | $info['pfsense_version'] = array( 520 | 'product_version_string'=> $g['product_version_string'], 521 | 'product_version'=>$g['product_version'], 522 | 'product_version_patch'=>$g['product_version_patch'], 523 | ); 524 | $info['pfsense_remote_version'] = NULL; 525 | $remote_system_version = get_system_pkg_version(false, true); 526 | if (is_array($remote_system_version) ) { 527 | $info['pfsense_remote_version']= $remote_system_version; 528 | } 529 | $info['os_verison'] = php_uname("s") . " " . php_uname("r"); 530 | $info['cpu_type'] = array ( 531 | 'cpu_model' => get_single_sysctl("hw.model"), 532 | 'cpu_count' => get_cpu_count(), 533 | 'logic_cpu_count' => get_cpu_count(true), 534 | 'cpu_freq' => get_cpufreq(), 535 | ); 536 | $info['kernel_pti_status'] = get_single_sysctl('vm.pmap.pti')==0 ?'disabled':'enabled'; 537 | $info['mds_mitigation'] = get_single_sysctl('hw.mds_disable_state'); 538 | exec('/bin/kenv -q smbios.bios.vendor 2>/dev/null', $biosvendor); 539 | exec('/bin/kenv -q smbios.bios.version 2>/dev/null', $biosversion); 540 | exec('/bin/kenv -q smbios.bios.reldate 2>/dev/null', $biosdate); 541 | $info['bios'] = array ( 542 | 'vendor' => $biosvendor[0], 543 | 'version' => $biosversion[0], 544 | 'date' => $biosdate[0], 545 | ); 546 | return $info; 547 | } 548 | 549 | /** 550 | * system_stats 551 | * 552 | * @return array 553 | */ 554 | public function system_stats() { 555 | fauxApiLogger::debug(__METHOD__); 556 | 557 | include_once '/usr/local/www/includes/functions.inc.php'; 558 | 559 | // Mostly as per the get_stats() in functions.inc.php 560 | // NB: calls are now wrapped in function_exists() because some functions 561 | // have been removed in later versions of pfSense 562 | 563 | if (function_exists('cpu_usage')) { 564 | $stats['cpu'] = strip_tags(\cpu_usage()); 565 | } 566 | if (function_exists('mem_usage')) { 567 | $stats['mem'] = strip_tags(\mem_usage()); 568 | } 569 | if (function_exists('get_uptime')) { 570 | $stats['uptime'] = strip_tags(\get_uptime()); 571 | } 572 | if (function_exists('get_pfstate')) { 573 | $stats['pfstate'] = strip_tags(\get_pfstate()); 574 | $stats['pfstatepercent'] = strip_tags(\get_pfstate(true)); 575 | } 576 | if (function_exists('get_temp')) { 577 | $stats['temp'] = strip_tags(\get_temp()); 578 | } 579 | if (function_exists('update_date_time')) { 580 | $stats['datetime'] = gmdate('Ymd\ZHis', strtotime((\update_date_time()))); 581 | } 582 | if (function_exists('get_interfacestats')) { 583 | $stats['interfacestatistics'] = strip_tags(\get_interfacestats()); 584 | } 585 | if (function_exists('get_interfacestatus')) { 586 | $stats['interfacestatus'] = strip_tags(\get_interfacestatus()); 587 | } 588 | if (function_exists('get_cpufreq')) { 589 | $stats['cpufreq'] = strip_tags(\get_cpufreq()); 590 | } 591 | if (function_exists('get_load_average')) { 592 | $stats['load_average'] = explode(',',str_replace(' ','',strip_tags(\get_load_average()))); 593 | } 594 | 595 | if (function_exists('get_mbuf')) { 596 | 597 | // Might be the worst hack work-around in history, but I'd love to 598 | // understand why pfSense developers decided to make the get_mbuf() 599 | // function a pass-by-reference function in 2.4.x - if there is a 600 | // better way than this, drop me line! 601 | 602 | $content = file_get_contents('/usr/local/www/includes/functions.inc.php'); 603 | if (strpos($content, 'function get_mbuf(&$mbuf')) { 604 | // pfSense 2.4.x 605 | \get_mbuf($stats['mbuf'], $stats['mbufpercent']); 606 | $stats['mbuf'] = strip_tags($stats['mbuf']); 607 | $stats['mbufpercent'] = strip_tags($stats['mbufpercent']); 608 | } else { 609 | // pfSense 2.3.x 610 | $stats['mbuf'] = strip_tags(\get_mbuf()); 611 | $stats['mbufpercent'] = strip_tags(\get_mbuf(true)); 612 | 613 | } 614 | } 615 | 616 | return $stats; 617 | } 618 | 619 | /** 620 | * rule_get 621 | * 622 | * @param int $rule_number 623 | * @return array 624 | */ 625 | public function rule_get($rule_number=NULL) { 626 | fauxApiLogger::debug(__METHOD__, array( 627 | 'rule_number' => $rule_number 628 | )); 629 | 630 | $rules_temp_file = tempnam(sys_get_temp_dir(), 'fauxApi_'); 631 | $exec_command = 'pfctl -sr -vv > "'.$rules_temp_file.'"'; 632 | fauxApiLogger::debug('exec pfctl', array( 633 | 'exec_command' => $exec_command 634 | )); 635 | exec($exec_command); 636 | 637 | if(!is_file($rules_temp_file)) { 638 | fauxApiLogger::error('unable to locate output temp file from exec pfctl call', array( 639 | 'rules_temp_file' => $rules_temp_file, 640 | )); 641 | return array(); 642 | } 643 | 644 | $rules_temp_file_content = file_get_contents($rules_temp_file); 645 | unlink($rules_temp_file); 646 | 647 | $rule = NULL; 648 | $rules = array(); 649 | foreach(explode("\n",$rules_temp_file_content) as $line) { 650 | if('@' === substr($line, 0, 1)) { 651 | preg_match('/^\@([0-9]+)\(\d+\) (.*?)$/', $line, $m); 652 | if(!isset($m[1]) || !isset($m[2])) { 653 | fauxApiLogger::error('problem parsing pfctl rules output rule-line', array( 654 | 'line' => $line, 655 | )); 656 | return array(); 657 | } else { 658 | $rule = (int)$m[1]; 659 | } 660 | $rules[$rule] = array( 661 | 'rule' => $m[2] 662 | ); 663 | } elseif (strpos($line,'[') && strpos($line,']')) { 664 | // Hack to make the output more easily parseable 665 | $line = str_replace(': pid ', ': ', $line); 666 | $line = str_replace('State Creations: ', 'State_Creations: ', $line); 667 | $line = strtolower($line); 668 | 669 | preg_match_all('/(\w+)\: ([0-9]+)/', $line, $m); 670 | if(!isset($m[1]) || !isset($m[2])) { 671 | fauxApiLogger::error('problem parsing pfctl rules output stats-line', array( 672 | 'line' => $line, 673 | )); 674 | return array(); 675 | } else { 676 | $rules[$rule]['number'] = $rule; 677 | foreach($m[1] as $index=>$attribute) { 678 | $rules[$rule][$attribute] = $m[2][$index]; 679 | } 680 | } 681 | } 682 | } 683 | 684 | if(!empty($rule_number)) { 685 | if(!array_key_exists($rule_number, $rules)) { 686 | fauxApiLogger::error('unable to locate rule', array( 687 | 'rule_number' => $rule_number 688 | )); 689 | return array(); 690 | } 691 | return array($rules[$rule_number]); 692 | } 693 | return $rules; 694 | } 695 | 696 | /** 697 | * alias_update_urltables() 698 | * 699 | * @param string $table 700 | * @return array 701 | * @note based on /etc/rc.update_urltables 702 | */ 703 | public function alias_update_urltables($table=NULL) { 704 | fauxApiLogger::debug(__METHOD__, array( 705 | 'table' => $table 706 | )); 707 | 708 | include_once '/etc/inc/config.inc'; 709 | include_once '/etc/inc/util.inc'; 710 | include_once '/etc/inc/pfsense-utils.inc'; 711 | 712 | $config = $this->config_load($this->config_default_filename); 713 | 714 | $urltables = array(); 715 | 716 | if ( 717 | array_key_exists('aliases', $config) and 718 | is_array($config['aliases']) and 719 | array_key_exists('aliases', $config['aliases'])) { 720 | foreach ($config['aliases']['alias'] as $alias) { 721 | if (preg_match('/urltable/i', $alias['type'])) { 722 | $urltables[$alias['name']] = array( 723 | 'url' => $alias['url'], 724 | 'type' => $alias['type'], 725 | 'updatefreq' => $alias['updatefreq'] 726 | ); 727 | } 728 | } 729 | } 730 | 731 | $updates = array(); 732 | foreach ($urltables as $urltablename => $urltable) { 733 | $updates[$urltablename] = array('url' => $urltable['url']); 734 | if (empty($table) || $table === $urltablename) { 735 | $result = \process_alias_urltable( 736 | $urltablename, $urltable['type'], $urltable['url'], $urltable['updatefreq'], TRUE 737 | ); 738 | if ((int)$result === 1) { 739 | $exec_return = ''; 740 | if ($urltable['type'] === 'urltable') { 741 | exec("/sbin/pfctl -t " . escapeshellarg($urltablename) . " -T replace -f /var/db/aliastables/" . escapeshellarg($urltablename) . ".txt 2>&1", $exec_return); 742 | } 743 | $updates[$urltablename]['status'] = $exec_return; 744 | fauxApiLogger::debug('Updated '.$urltablename.' content from '.$urltable['url'], $exec_return); 745 | } 746 | } 747 | } 748 | return $updates; 749 | } 750 | 751 | /** 752 | * gateway_status() 753 | * 754 | * @return array 755 | * @note calls pfsense return_gateways_status() from gwlb.inc 756 | */ 757 | public function gateway_status() { 758 | fauxApiLogger::debug(__METHOD__); 759 | 760 | include_once '/etc/inc/gwlb.inc'; 761 | 762 | $byname = false; 763 | return \return_gateways_status($byname); 764 | } 765 | 766 | /** 767 | * interface_stats() 768 | * 769 | * @param array $interface 770 | * @return array 771 | */ 772 | public function interface_stats($interface){ 773 | fauxApiLogger::debug(__METHOD__); 774 | 775 | if(is_null($interface)) { 776 | $error_message = 'no interface parameter provided'; 777 | fauxApiLogger::error($error_message); 778 | throw new \Exception($error_message); 779 | } 780 | 781 | return \pfSense_get_interface_stats($interface); 782 | } 783 | 784 | /** 785 | * function_call() 786 | * 787 | * @param string $function 788 | * @param array $args 789 | * @param array $includes 790 | * @return array 791 | * @note calls any (or most) functions available in pfSense if whitelisted in /etc/fauxapi_pfsense_interface.txt 792 | */ 793 | public function function_call($function, $args, $includes) { 794 | fauxApiLogger::debug(__METHOD__, array( 795 | 'function' => $function, 796 | 'args' => $args, 797 | 'includes' => $includes, 798 | )); 799 | 800 | if(!is_file($this->config_pfsense_function_calls)) { 801 | $error_message = 'required pfsense function calls file not found'; 802 | $error_data = array( 803 | 'filename' => $this->config_pfsense_function_calls 804 | ); 805 | fauxApiLogger::error($error_message, $error_data); 806 | throw new \Exception($error_message); 807 | } 808 | 809 | // load and parse pfsense_function_calls.txt 810 | $pf_function_calls_raw = file_get_contents($this->config_pfsense_function_calls); 811 | $pf_function_calls = array(); 812 | $pf_include_files = array(); 813 | foreach(explode("\n", str_replace("\r",'',$pf_function_calls_raw)) as $line) { 814 | if(strlen($line) > 1 && strpos($line, '#') === FALSE && strpos($line, ':') !== FALSE ) { 815 | $ln_parts = explode(':', rtrim($line)); 816 | $include_candidate = $this->config_pfsense_include_path . '/' . fauxApiUtils::sanitize($ln_parts[0], array('.', '-', '_')); 817 | $include_candidate = str_replace('..','',$include_candidate); 818 | if(!is_file($include_candidate)) { 819 | fauxApiLogger::warn('skipping include file because not found', array( 820 | 'include' => $include_candidate, 821 | )); 822 | continue 1; 823 | } 824 | if(!in_array($include_candidate, $pf_include_files)) { 825 | $pf_include_files[] = $include_candidate; 826 | } 827 | if(count($ln_parts) !== 2) { 828 | continue 1; 829 | } 830 | $fn_parts = ltrim(str_replace('function', '', $ln_parts[1])); 831 | preg_match('/^(.*?)\((.*?)\)/', $fn_parts, $fn_matches); 832 | if(count($fn_matches) < 2) { 833 | continue 1; 834 | } 835 | if(isset($fn_matches[2]) && strlen($fn_matches[2]) > 0) { 836 | $fn_args = explode(',',str_replace(' ','', $fn_matches[2])); 837 | } else { 838 | $fn_args = array(); 839 | } 840 | $pf_function_calls[] = array( 841 | 'include' => $include_candidate, 842 | 'function' => $fn_matches[1], 843 | 'args' => $fn_args, 844 | ); 845 | } 846 | } 847 | 848 | // check if the function is valid and determine the include file it comes from 849 | $pass = FALSE; 850 | $fn_args = array(); 851 | $fn_include = NULL; 852 | foreach($pf_function_calls as $pf_function_call) { 853 | if($pf_function_call['function'] === $function) { 854 | $pass = TRUE; 855 | $fn_args = $pf_function_call['args']; 856 | $fn_include = $pf_function_call['include']; 857 | break; 858 | } 859 | } 860 | if(FALSE === $pass) { 861 | $error_message = 'function not defined as valid in function calls reference file'; 862 | $error_data = array( 863 | 'function' => $function, 864 | 'function calls reference file' => $this->config_pfsense_function_calls, 865 | ); 866 | fauxApiLogger::error($error_message, $error_data); 867 | throw new \Exception($error_message); 868 | } 869 | 870 | // check if the number of args appears to be appropriate 871 | $arg_count_required = 0; 872 | $arg_count_optional = 0; 873 | foreach($fn_args as $fn_arg) { 874 | if(!strstr($fn_arg, '=')) { 875 | $arg_count_required++; 876 | } else { 877 | $arg_count_optional++; 878 | } 879 | } 880 | if(count($args) < $arg_count_required || count($args) > $arg_count_required + $arg_count_optional) { 881 | $error_message = 'incorrect number of function args provided'; 882 | $error_data = array( 883 | 'function' => $function, 884 | 'args' => $args, 885 | ); 886 | fauxApiLogger::error($error_message, $error_data); 887 | throw new \Exception($error_message); 888 | } 889 | 890 | // process the user supplied includes first 891 | foreach($includes as $include) { 892 | $include_candidate = $this->config_pfsense_include_path . '/' . fauxApiUtils::sanitize($include, array('.', '-', '_')); 893 | $include_candidate = str_replace('..','',$include_candidate); 894 | if(in_array($include_candidate, $pf_include_files) && is_file($include_candidate)) { 895 | fauxApiLogger::debug('include_once('.$include.') by user includes'); 896 | include_once($include_candidate); 897 | } else { 898 | $error_message = 'failed to include file for function call'; 899 | $error_data = array( 900 | 'function' => $function, 901 | 'include file' => $include_candidate, 902 | ); 903 | fauxApiLogger::error($error_message, $error_data); 904 | throw new \Exception($error_message); 905 | } 906 | } 907 | 908 | // include the file for the underlaying function 909 | if(is_file($fn_include)) { 910 | fauxApiLogger::debug('include_once('.$fn_include.') implied by function'); 911 | include_once($fn_include); 912 | } else { 913 | $error_message = 'failed to include implied include file for function call'; 914 | $error_data = array( 915 | 'function' => $function, 916 | 'include file' => $fn_include, 917 | ); 918 | fauxApiLogger::error($error_message, $error_data); 919 | throw new \Exception($error_message); 920 | } 921 | 922 | // issue the function_call with supplied args 923 | return call_user_func_array($function, $args); 924 | } 925 | 926 | } 927 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FauxAPI - v1.4 2 | A REST API interface for pfSense 2.3.x, 2.4.x, 2.5.x to facilitate devops:- 3 | - https://github.com/ndejong/pfsense_fauxapi 4 | 5 | Additionally available are a set of [client libraries](#client-libraries) 6 | that hence make programmatic access and management of pfSense hosts for devops 7 | tasks feasible. 8 | 9 | ### Important 10 | * You MUST (manually) setup your `/etc/fauxapi/credentials.ini` file on the pfSense host before 11 | you continue, see the [API Authentication](#api-authentication) section below. 12 | * You MUST (manually) setup a `/etc/fauxapi/pfsense_function_calls.txt` file if you want to use 13 | the `function_call` API method. You may wish to copy the sample `/etc/fauxapi/pfsense_function_calls.sample.txt` 14 | as a starting point. 15 | 16 | ## API Action Summary 17 | - [alias_update_urltables](#user-content-alias_update_urltables) - Causes the pfSense host to immediately update any urltable alias entries from their (remote) source URLs. 18 | - [config_backup](#user-content-config_backup) - Causes the system to take a configuration backup and add it to the regular set of system change backups. 19 | - [config_backup_list](#user-content-config_backup_list) - Returns a list of the currently available system configuration backups. 20 | - [config_get](#user-content-config_get) - Returns the full system configuration as a JSON formatted string. 21 | - [config_patch](#user-content-config_patch) - Patch the system config with a granular piece of new configuration. 22 | - [config_reload](#user-content-config_reload) - Causes the pfSense system to perform an internal reload of the `config.xml` file. 23 | - [config_restore](#user-content-config_restore) - Restores the pfSense system to the named backup configuration. 24 | - [config_set](#user-content-config_set) - Sets a full system configuration and (by default) reloads once successfully written and tested. 25 | - [function_call](#user-content-function_call) - Call directly a pfSense PHP function with API user supplied parameters. 26 | - [gateway_status](#user-content-gateway_status) - Returns gateway status data. 27 | - [interface_stats](#user-content-interface_stats) - Returns statistics and information about an interface. 28 | - [rule_get](#user-content-rule_get) - Returns the numbered list of loaded pf rules from a `pfctl -sr -vv` command on the pfSense host. 29 | - [send_event](#user-content-send_event) - Performs a pfSense "send_event" command to cause various pfSense system actions. 30 | - [system_reboot](#user-content-system_reboot) - Reboots the pfSense system. 31 | - [system_stats](#user-content-system_stats) - Returns various useful system stats. 32 | - [system_info](#user-content-system_info) - Returns various useful system info. 33 | 34 | 35 | ## Approach 36 | At its core FauxAPI simply reads the core pfSense `config.xml` file, converts it 37 | to JSON and returns to the API caller. Similarly it can take a JSON formatted 38 | configuration and write it to the pfSense `config.xml` and handles the required 39 | reload operations. The ability to programmatically interface with a running 40 | pfSense host(s) is enormously useful however it should also be obvious that this 41 | provides the API user the ability to create configurations that can break your 42 | pfSense system. 43 | 44 | FauxAPI provides easy backup and restore API interfaces that by default store 45 | configuration backups on all configuration write operations thus it is very easy 46 | to roll-back even if the API user manages to deploy a "very broken" configuration. 47 | 48 | Multiple sanity checks take place to make sure a user provided JSON config will 49 | correctly convert into the (slightly quirky) pfSense XML `config.xml` format and 50 | then reload as expected in the same way. However, because it is not a real 51 | per-action application-layer interface it is still possible for the API caller 52 | to create configuration changes that make no sense and can potentially disrupt 53 | your pfSense system - as the package name states, it is a "Faux" API to pfSense 54 | filling a gap in functionality with the current pfSense product. 55 | 56 | Because FauxAPI is a utility that interfaces with the pfSense `config.xml` there 57 | are some cases where reloading the configuration file is not enough and you 58 | may need to "tickle" pfSense a little more to do what you want. This is not 59 | common however a good example is getting newly defined network interfaces or 60 | VLANs to be recognized. These situations are easily handled by calling the 61 | **send_event** action with the payload **interface reload all** - see the example 62 | included below and refer to a the resolution to [Issue #10](https://github.com/ndejong/pfsense_fauxapi/issues/10) 63 | 64 | __NB:__ *As at FauxAPI v1.2 the **function_call** action has been introduced that 65 | now provides the ability to issue function calls directly into pfSense.* 66 | 67 | 68 | ## Installation 69 | Until the FauxAPI is added to the pfSense FreeBSD-ports tree you will need to 70 | install manually from **root** as shown:- 71 | ```bash 72 | set fauxapi_base_package_url='https://raw.githubusercontent.com/ndejong/pfsense_fauxapi_packages/master' 73 | set fauxapi_latest=`fetch -qo - ${fauxapi_base_package_url}/LATEST` 74 | fetch ${fauxapi_base_package_url}/${fauxapi_latest} 75 | pkg-static install ${fauxapi_latest} 76 | ``` 77 | 78 | Installation and de-installation is quite straight forward, further examples can 79 | be found in the `README.md` located [here](https://github.com/ndejong/pfsense_fauxapi_packages). 80 | 81 | Refer to the published package [`SHA256SUMS`](https://github.com/ndejong/pfsense_fauxapi_packages/blob/master/SHA256SUMS) 82 | 83 | **Hint:** if not already, consider installing the `jq` tool on your local machine (not 84 | pfSense host) to pipe and manage JSON outputs from FauxAPI - https://stedolan.github.io/jq/ 85 | 86 | **NB:** you MUST at least setup your `/etc/fauxapi/credentials.ini` file on the 87 | pfSense host before you continue, see the API Authentication section below. 88 | 89 | ## Client libraries 90 | 91 | #### Python 92 | A [Python interface](https://github.com/ndejong/pfsense_fauxapi_client_python) 93 | to pfSense was perhaps the most desired end-goal at the onset of the FauxAPI 94 | package project. Anyone that has tried to parse the pfSense `config.xml` files 95 | using a Python based library will understand that things don't quite work out as 96 | expected or desired. 97 | 98 | The Python client-library can be easily installed from PyPi as such 99 | ```bash 100 | pip3 install pfsense-fauxapi 101 | ``` 102 | 103 | Package Status: [![PyPi](https://img.shields.io/pypi/v/pfsense-fauxapi.svg)](https://pypi.org/project/pfsense-fauxapi/) [![Build Status](https://travis-ci.org/ndejong/pfsense_fauxapi_client_python.svg?branch=master)](https://travis-ci.org/ndejong/pfsense_fauxapi_client_python) 104 | 105 | Use of the package should be easy enough as shown 106 | ```python 107 | import pprint, sys 108 | from PfsenseFauxapi.PfsenseFauxapi import PfsenseFauxapi 109 | PfsenseFauxapi = PfsenseFauxapi('', '', '') 110 | 111 | aliases = PfsenseFauxapi.config_get('aliases') 112 | ## perform some kind of manipulation to `aliases` here ## 113 | pprint.pprint(PfsenseFauxapi.config_set(aliases, 'aliases')) 114 | ``` 115 | 116 | It is recommended to review the [Python code examples](https://github.com/ndejong/pfsense_fauxapi_client_python/tree/master/examples) 117 | to observe worked examples with the client library. Of small note is that the 118 | Python library supports the ability to get and set single sections of the pfSense 119 | system, not just the entire system configuration as with the Bash library. 120 | 121 | **Python examples** 122 | - `usergroup-management.py` - example code that provides the ability to `get_users`, 123 | `add_user`, `manage_user`, `remove_user` and perform the same functions on groups. 124 | - `update-aws-aliases.py` - example code that pulls in the latest AWS `ip-ranges.json` 125 | data, parses it and injects them into the pfSense aliases section if required. 126 | - `function-iterate.py` - iterates (almost) all the FauxAPI functions to 127 | confirm operation. 128 | 129 | #### Command Line 130 | As distinct from the Bash library as described below the Python pip also introduces 131 | a command-line tool to interact with the API, which makes a wide range of actions 132 | possible directly from the command line, for example 133 | ```bash 134 | fauxapi --host 192.168.1.200 gateway_status | jq . 135 | ``` 136 | 137 | #### Bash 138 | The [Bash client library](https://github.com/ndejong/pfsense_fauxapi_client_bash) 139 | makes it possible to add a line with `source pfsense-fauxapi.sh` to your bash script 140 | and then access a pfSense host configuration directly as a JSON string 141 | ```bash 142 | source pfsense-fauxapi.sh 143 | export fauxapi_auth=$(fauxapi_auth ) 144 | 145 | fauxapi_config_get | jq .data.config > /tmp/config.json 146 | ## perform some kind of manipulation to `/tmp/config.json` here ## 147 | fauxapi_config_set /tmp/config.json 148 | ``` 149 | 150 | It is recommended to review the commented out samples in the provided 151 | `fauxapi-sample.sh` file that cover all possible FauxAPI calls to gain a better 152 | idea on usage. 153 | 154 | #### NodeJS/TypeScript 155 | A NodeJS client has been developed by a third party and is available here 156 | - NPMJS: [npmjs.com/package/faux-api-client](https://www.npmjs.com/package/faux-api-client) 157 | - Github: [github.com/Elucidia/faux-api-client](https://github.com/Elucidia/faux-api-client) 158 | 159 | #### PHP 160 | A PHP client has been developed by a third party and is available here 161 | - Packagist: [packagist.org/packages/travisghansen/pfsense_fauxapi_php_client](https://packagist.org/packages/travisghansen/pfsense_fauxapi_php_client) 162 | - Github: [github.com/travisghansen/pfsense_fauxapi_php_client](https://github.com/travisghansen/pfsense_fauxapi_php_client) 163 | 164 | 165 | ## API Authentication 166 | A deliberate design decision to decouple FauxAPI authentication from both the 167 | pfSense user authentication and the pfSense `config.xml` system. This was done 168 | to limit the possibility of an accidental API change that removes access to the 169 | host. It also seems more prudent to only establish API user(s) manually via the 170 | FauxAPI `/etc/fauxapi/credentials.ini` file - happy to receive feedback about 171 | this approach. 172 | 173 | The two sample FauxAPI keys (PFFAexample01 and PFFAexample02) and their 174 | associated secrets in the sample `credentials.sample.ini` file are hard-coded to 175 | be inoperative, you must create entirely new values before your client scripts 176 | will be able to issue commands to FauxAPI. 177 | 178 | You can start your own `/etc/fauxapi/credentials.ini` file by copying the sample 179 | file provided in `credentials.sample.ini` 180 | 181 | API authentication itself is performed on a per-call basis with the auth value 182 | inserted as an additional **fauxapi-auth** HTTP request header, it can be 183 | calculated as such:- 184 | ``` 185 | fauxapi-auth: ::: 186 | 187 | For example:- 188 | fauxapi-auth: PFFA4797d073:20161119Z144328:833a45d8:9c4f96ab042f5140386178618be1ae40adc68dd9fd6b158fb82c99f3aaa2bb55 189 | ``` 190 | 191 | Where the <hash> value is calculated like so:- 192 | ``` 193 | = sha256() 194 | ``` 195 | 196 | NB: that the timestamp value is internally passed to the PHP `strtotime` function 197 | which can interpret a wide variety of timestamp formats together with a timezone. 198 | A nice tidy timestamp format that the `strtotime` PHP function is able to process 199 | can be obtained using bash command `date --utc +%Y%m%dZ%H%M%S` where the `Z` 200 | date-time seperator hence also specifies the UTC timezone. 201 | 202 | This is all handled in the [client libraries](#client-libraries) 203 | provided, but as can be seen it is relatively easy to implement even in a Bash 204 | shell script. 205 | 206 | Getting the API credentials right seems to be a common source of confusion in 207 | getting started with FauxAPI because the rules about valid API keys and secret 208 | values are pedantic to help make ensure poor choices are not made. 209 | 210 | The API key + API secret values that you will need to create in `/etc/fauxapi/credentials.ini` 211 | have the following rules:- 212 | - and may have alphanumeric chars ONLY! 213 | - MUST start with the prefix PFFA (pfSense Faux API) 214 | - MUST be >= 12 chars AND <= 40 chars in total length 215 | - MUST be >= 40 chars AND <= 128 chars in length 216 | - you must not use the sample key/secret in the `credentials.ini` since they 217 | are hard coded to fail. 218 | 219 | To make things easier consider using the following shell commands to generate 220 | valid values:- 221 | 222 | #### apikey_value 223 | ```bash 224 | echo PFFA`head /dev/urandom | base64 -w0 | tr -d /+= | head -c 20` 225 | ``` 226 | 227 | #### apisecret_value 228 | ```bash 229 | echo `head /dev/urandom | base64 -w0 | tr -d /+= | head -c 60` 230 | ``` 231 | 232 | NB: Make sure the client side clock is within 60 seconds of the pfSense host 233 | clock else the auth token values calculated by the client will not be valid - 60 234 | seconds seems tight, however, provided you are using NTP to look after your 235 | system time it's quite unlikely to cause issues - happy to receive feedback 236 | about this. 237 | 238 | __Shout Out:__ *Seeking feedback on the API authentication, many developers 239 | seem to stumble here - if you feel something could be improved without compromising 240 | security then submit an Issue ticket via Github.* 241 | 242 | 243 | ## API Authorization 244 | The file `/etc/fauxapi/credentials.ini` additionally provides a method to restrict 245 | the API actions available to the API key using the **permit** configuration 246 | parameter. Permits are comma delimited and may contain * wildcards to match more 247 | than one rule as shown in the example below. 248 | 249 | ``` 250 | [PFFAexample01] 251 | secret = abcdefghijklmnopqrstuvwxyz0123456789abcd 252 | permit = alias_*, config_*, gateway_*, rule_*, send_*, system_*, function_* 253 | comment = example key PFFAexample01 - hardcoded to be inoperative 254 | ``` 255 | 256 | ## Debugging 257 | FauxAPI comes with awesome debug logging capability, simply insert `__debug=true` 258 | as a URL request parameter and the response data will contain rich debugging log 259 | data about the flow of the request. 260 | 261 | If you are looking for more debugging at various points feel free to submit a 262 | pull request or lodge an issue describing your requirement and I'll see what 263 | can be done to accommodate. 264 | 265 | 266 | ## Logging 267 | FauxAPI actions are sent to the system syslog via a call to the PHP `syslog()` 268 | function thus causing all FauxAPI actions to be logged and auditable on a per 269 | action (callid) basis which provide the full basis for the call, for example:- 270 | ```text 271 | Jul 3 04:37:59 pfSense php-fpm[55897]: {"INFO":"20180703Z043759 :: fauxapi\\v1\\fauxApi::__call","DATA":{"user_action":"alias_update_urltables","callid":"5b3afda73e7c9","client_ip":"192.168.1.5"},"source":"fauxapi"} 272 | Jul 3 04:37:59 pfSense php-fpm[55897]: {"INFO":"20180703Z043759 :: valid auth for call","DATA":{"apikey":"PFFAdevtrash","callid":"5b3afda73e7c9","client_ip":"192.168.1.5"},"source":"fauxapi"} 273 | ``` 274 | Enabling debugging yields considerably more logging data to assist with tracking 275 | down issues if you encounter them - you may review the logs via the pfSense GUI 276 | as usual unser Status->System Logs->General or via the console using the `clog` tool 277 | ```bash 278 | $ clog /var/log/system.log | grep fauxapi 279 | ``` 280 | 281 | ## Configuration Backups 282 | All configuration edits through FauxAPI create configuration backups in the 283 | same way as pfSense does with the webapp GUI. 284 | 285 | These backups are available in the same way as edits through the pfSense 286 | GUI and are thus able to be reviewed and diff'd in the same way under 287 | Diagnostics->Backup & Restore->Config History. 288 | 289 | Changes made through the FauxAPI carry configuration change descriptions that 290 | name the unique `callid` which can then be tied to logs if required for full 291 | usage audit and change tracking. 292 | 293 | FauxAPI functions that cause write operations to the system config `config.xml` 294 | return reference to a backup file of the configuration immediately previous 295 | to the change. 296 | 297 | 298 | ## API REST Actions 299 | The following REST based API actions are provided, example cURL call request 300 | examples are provided for each. The API user is perhaps more likely to interface 301 | with the [client libraries](#client-libraries) as documented above 302 | rather than directly with these REST end-points. 303 | 304 | The framework around the FauxAPI has been put together with the idea of being 305 | able to easily add more actions at a later time, if you have ideas for actions 306 | that might be useful be sure to get in contact. 307 | 308 | NB: the cURL requests below use the '--insecure' switch because many pfSense 309 | deployments do not deploy certificate chain signed SSL certificates. A reasonable 310 | improvement in this regard might be to implement certificate pinning at the 311 | client side to hence remove scope for man-in-middle concerns. 312 | 313 | --- 314 | ### alias_update_urltables 315 | - Causes the pfSense host to immediately update any urltable alias entries from their (remote) 316 | source URLs. Optionally update just one table by specifying the table name, else all 317 | tables are updated. 318 | - HTTP: **GET** 319 | - Params: 320 | - **table** (optional, default = null) 321 | 322 | *Example Request* 323 | ```bash 324 | curl \ 325 | -X GET \ 326 | --silent \ 327 | --insecure \ 328 | --header "fauxapi-auth: " \ 329 | "https:///fauxapi/v1/?action=alias_update_urltables" 330 | ``` 331 | 332 | *Example Response* 333 | ```javascript 334 | { 335 | "callid": "598ec756b4d09", 336 | "action": "alias_update_urltables", 337 | "message": "ok", 338 | "data": { 339 | "updates": { 340 | "bruteforceblocker": { 341 | "url": "https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/bruteforceblocker.ipset", 342 | "status": [ 343 | "no changes." 344 | ] 345 | } 346 | } 347 | } 348 | } 349 | ``` 350 | 351 | --- 352 | ### config_backup 353 | - Causes the system to take a configuration backup and add it to the regular 354 | set of pfSense system backups at `/cf/conf/backup/` 355 | - HTTP: **GET** 356 | - Params: none 357 | 358 | *Example Request* 359 | ```bash 360 | curl \ 361 | -X GET \ 362 | --silent \ 363 | --insecure \ 364 | --header "fauxapi-auth: " \ 365 | "https:///fauxapi/v1/?action=config_backup" 366 | ``` 367 | 368 | *Example Response* 369 | ```javascript 370 | { 371 | "callid": "583012fea254f", 372 | "action": "config_backup", 373 | "message": "ok", 374 | "data": { 375 | "backup_config_file": "/cf/conf/backup/config-1479545598.xml" 376 | } 377 | } 378 | ``` 379 | 380 | --- 381 | ### config_backup_list 382 | - Returns a list of the currently available pfSense system configuration backups. 383 | - HTTP: **GET** 384 | - Params: none 385 | 386 | *Example Request* 387 | ```bash 388 | curl \ 389 | -X GET \ 390 | --silent \ 391 | --insecure \ 392 | --header "fauxapi-auth: " \ 393 | "https:///fauxapi/v1/?action=config_backup_list" 394 | ``` 395 | 396 | *Example Response* 397 | ```javascript 398 | { 399 | "callid": "583065cb670db", 400 | "action": "config_backup_list", 401 | "message": "ok", 402 | "data": { 403 | "backup_files": [ 404 | { 405 | "filename": "/cf/conf/backup/config-1479545598.xml", 406 | "timestamp": "20161119Z144635", 407 | "description": "fauxapi-PFFA4797d073@192.168.10.10: update via fauxapi for callid: 583012fea254f", 408 | "version": "15.5", 409 | "filesize": 18535 410 | }, 411 | .... 412 | ``` 413 | 414 | --- 415 | ### config_get 416 | - Returns the system configuration as a JSON formatted string. Additionally, 417 | using the optional **config_file** parameter it is possible to retrieve backup 418 | configurations by providing the full path to it under the `/cf/conf/backup` 419 | path. 420 | - HTTP: **GET** 421 | - Params: 422 | - **config_file** (optional, default=`/cf/config/config.xml`) 423 | 424 | *Example Request* 425 | ```bash 426 | curl \ 427 | -X GET \ 428 | --silent \ 429 | --insecure \ 430 | --header "fauxapi-auth: " \ 431 | "https:///fauxapi/v1/?action=config_get" 432 | ``` 433 | 434 | *Example Response* 435 | ```javascript 436 | { 437 | "callid": "583012fe39f79", 438 | "action": "config_get", 439 | "message": "ok", 440 | "data": { 441 | "config_file": "/cf/conf/config.xml", 442 | "config": { 443 | "version": "15.5", 444 | "staticroutes": "", 445 | "snmpd": { 446 | "syscontact": "", 447 | "rocommunity": "public", 448 | "syslocation": "" 449 | }, 450 | "shaper": "", 451 | "installedpackages": { 452 | "pfblockerngsouthamerica": { 453 | "config": [ 454 | .... 455 | ``` 456 | 457 | Hint: use `jq` to parse the response JSON and obtain the config only, as such:- 458 | ```bash 459 | cat /tmp/faux-config-get-output-from-curl.json | jq .data.config > /tmp/config.json 460 | ``` 461 | 462 | --- 463 | ### config_patch 464 | - Allows the API user to patch the system configuration with the existing system config 465 | - A **config_patch** call allows the API user to supply the partial configuration to be updated 466 | which is quite different to the **config_set** function that requires the full configuration 467 | to be posted. 468 | - HTTP: **POST** 469 | - Params: 470 | - **do_backup** (optional, default = true) 471 | - **do_reload** (optional, default = true) 472 | 473 | *Example Request* 474 | ```bash 475 | cat > /tmp/config_patch.json <" \ 492 | --header "Content-Type: application/json" \ 493 | --data @/tmp/config_patch.json \ 494 | "https:///fauxapi/v1/?action=config_patch" 495 | ``` 496 | 497 | *Example Response* 498 | ```javascript 499 | { 500 | "callid": "5b3b506f72670", 501 | "action": "config_patch", 502 | "message": "ok", 503 | "data": { 504 | "do_backup": true, 505 | "do_reload": true, 506 | "previous_config_file": "/cf/conf/backup/config-1530613871.xml" 507 | } 508 | ``` 509 | 510 | --- 511 | ### config_reload 512 | - Causes the pfSense system to perform a reload action of the `config.xml` file, by 513 | default this happens when the **config_set** action occurs hence there is 514 | normally no need to explicitly call this after a **config_set** action. 515 | - HTTP: **GET** 516 | - Params: none 517 | 518 | *Example Request* 519 | ```bash 520 | curl \ 521 | -X GET \ 522 | --silent \ 523 | --insecure \ 524 | --header "fauxapi-auth: " \ 525 | "https:///fauxapi/v1/?action=config_reload" 526 | ``` 527 | 528 | *Example Response* 529 | ```javascript 530 | { 531 | "callid": "5831226e18326", 532 | "action": "config_reload", 533 | "message": "ok" 534 | } 535 | ``` 536 | 537 | --- 538 | ### config_restore 539 | - Restores the pfSense system to the named backup configuration. 540 | - HTTP: **GET** 541 | - Params: 542 | - **config_file** (required, full path to the backup file to restore) 543 | 544 | *Example Request* 545 | ```bash 546 | curl \ 547 | -X GET \ 548 | --silent \ 549 | --insecure \ 550 | --header "fauxapi-auth: " \ 551 | "https:///fauxapi/v1/?action=config_restore&config_file=/cf/conf/backup/config-1479545598.xml" 552 | ``` 553 | 554 | *Example Response* 555 | ```javascript 556 | { 557 | "callid": "583126192a789", 558 | "action": "config_restore", 559 | "message": "ok", 560 | "data": { 561 | "config_file": "/cf/conf/backup/config-1479545598.xml" 562 | } 563 | } 564 | ``` 565 | 566 | --- 567 | ### config_set 568 | - Sets a full system configuration and (by default) takes a system config 569 | backup and (by default) causes the system config to be reloaded once 570 | successfully written and tested. 571 | - NB1: be sure to pass the *FULL* system configuration here, not just the piece you 572 | wish to adjust! Consider the **config_patch** or **config_item_set** functions if 573 | you wish to adjust the configuration in more granular ways. 574 | - NB2: if you are pulling down the result of a `config_get` call, be sure to parse that 575 | response data to obtain the config data only under the key `.data.config` 576 | - HTTP: **POST** 577 | - Params: 578 | - **do_backup** (optional, default = true) 579 | - **do_reload** (optional, default = true) 580 | 581 | *Example Request* 582 | ```bash 583 | curl \ 584 | -X POST \ 585 | --silent \ 586 | --insecure \ 587 | --header "fauxapi-auth: " \ 588 | --header "Content-Type: application/json" \ 589 | --data @/tmp/config.json \ 590 | "https:///fauxapi/v1/?action=config_set" 591 | ``` 592 | 593 | *Example Response* 594 | ```javascript 595 | { 596 | "callid": "5b3b50e8b1bc6", 597 | "action": "config_set", 598 | "message": "ok", 599 | "data": { 600 | "do_backup": true, 601 | "do_reload": true, 602 | "previous_config_file": "/cf/conf/backup/config-1530613992.xml" 603 | } 604 | } 605 | ``` 606 | 607 | --- 608 | ### function_call 609 | - Call directly a pfSense PHP function with API user supplied parameters. Note 610 | that is action is a *VERY* raw interface into the inner workings of pfSense 611 | and it is not recommended for API users that do not have a solid understanding 612 | of PHP and pfSense. Additionally, not all pfSense functions are appropriate 613 | to be called through the FauxAPI and only very limited testing has been 614 | performed against the possible outcomes and responses. It is possible to 615 | harm your pfSense system if you do not 100% understand what is going on. 616 | - Functions to be called via this interface *MUST* be defined in the file 617 | `/etc/pfsense_function_calls.txt` only a handful very basic and 618 | read-only pfSense functions are enabled by default. 619 | - You can start your own `/etc/fauxapi/pfsense_function_calls.txt` file by 620 | copying the sample file provided in `pfsense_function_calls.sample.txt` 621 | - HTTP: **POST** 622 | - Params: none 623 | 624 | *Example Request* 625 | ```bash 626 | curl \ 627 | -X POST \ 628 | --silent \ 629 | --insecure \ 630 | --header "fauxapi-auth: " \ 631 | --header "Content-Type: application/json" \ 632 | --data "{\"function\": \"get_services\"}" \ 633 | "https:///fauxapi/v1/?action=function_call" 634 | ``` 635 | 636 | *Example Response* 637 | ```javascript 638 | { 639 | "callid": "59a29e5017905", 640 | "action": "function_call", 641 | "message": "ok", 642 | "data": { 643 | "return": [ 644 | { 645 | "name": "unbound", 646 | "description": "DNS Resolver" 647 | }, 648 | { 649 | "name": "ntpd", 650 | "description": "NTP clock sync" 651 | }, 652 | .... 653 | 654 | ``` 655 | 656 | --- 657 | ### gateway_status 658 | - Returns gateway status data. 659 | - HTTP: **GET** 660 | - Params: none 661 | 662 | *Example Request* 663 | ```bash 664 | curl \ 665 | -X GET \ 666 | --silent \ 667 | --insecure \ 668 | --header "fauxapi-auth: " \ 669 | "https:///fauxapi/v1/?action=gateway_status" 670 | ``` 671 | 672 | *Example Response* 673 | ```javascript 674 | { 675 | "callid": "598ecf3e7011e", 676 | "action": "gateway_status", 677 | "message": "ok", 678 | "data": { 679 | "gateway_status": { 680 | "10.22.33.1": { 681 | "monitorip": "8.8.8.8", 682 | "srcip": "10.22.33.100", 683 | "name": "GW_WAN", 684 | "delay": "4.415ms", 685 | "stddev": "3.239ms", 686 | "loss": "0.0%", 687 | "status": "none" 688 | } 689 | } 690 | } 691 | } 692 | ``` 693 | 694 | --- 695 | ### interface_stats 696 | - Returns interface statistics data and information - the real interface name must be provided 697 | not an alias of the interface such as "WAN" or "LAN" 698 | - HTTP: **GET** 699 | - Params: 700 | - **interface** (required) 701 | 702 | *Example Request* 703 | ```bash 704 | curl \ 705 | -X GET \ 706 | --silent \ 707 | --insecure \ 708 | --header "fauxapi-auth: " \ 709 | "https:///fauxapi/v1/?action=interface_stats&interface=em0" 710 | ``` 711 | 712 | *Example Response* 713 | ```javascript 714 | { 715 | "callid": "5b3a5bce65d01", 716 | "action": "interface_stats", 717 | "message": "ok", 718 | "data": { 719 | "stats": { 720 | "inpkts": 267017, 721 | "inbytes": 21133408, 722 | "outpkts": 205860, 723 | "outbytes": 8923046, 724 | "inerrs": 0, 725 | "outerrs": 0, 726 | "collisions": 0, 727 | "inmcasts": 61618, 728 | "outmcasts": 73, 729 | "unsuppproto": 0, 730 | "mtu": 1500 731 | } 732 | } 733 | } 734 | ``` 735 | 736 | --- 737 | ### rule_get 738 | - Returns the numbered list of loaded pf rules from a `pfctl -sr -vv` command 739 | on the pfSense host. An empty rule_number parameter causes all rules to be 740 | returned. 741 | - HTTP: **GET** 742 | - Params: 743 | - **rule_number** (optional, default = null) 744 | 745 | *Example Request* 746 | ```bash 747 | curl \ 748 | -X GET \ 749 | --silent \ 750 | --insecure \ 751 | --header "fauxapi-auth: " \ 752 | "https:///fauxapi/v1/?action=rule_get&rule_number=5" 753 | ``` 754 | 755 | *Example Response* 756 | ```javascript 757 | { 758 | "callid": "583c279b56958", 759 | "action": "rule_get", 760 | "message": "ok", 761 | "data": { 762 | "rules": [ 763 | { 764 | "number": 5, 765 | "rule": "anchor \"openvpn/*\" all", 766 | "evaluations": "14134", 767 | "packets": "0", 768 | "bytes": "0", 769 | "states": "0", 770 | "inserted": "21188", 771 | "statecreations": "0" 772 | } 773 | ] 774 | } 775 | } 776 | ``` 777 | 778 | --- 779 | ### send_event 780 | - Performs a pfSense "send_event" command to cause various pfSense system 781 | actions as is also available through the pfSense console interface. The 782 | following standard pfSense send_event combinations are permitted:- 783 | - filter: reload, sync 784 | - interface: all, newip, reconfigure 785 | - service: reload, restart, sync 786 | - HTTP: **POST** 787 | - Params: none 788 | 789 | *Example Request* 790 | ```bash 791 | curl \ 792 | -X POST \ 793 | --silent \ 794 | --insecure \ 795 | --header "fauxapi-auth: " \ 796 | --header "Content-Type: application/json" \ 797 | --data "[\"interface reload all\"]" \ 798 | "https:///fauxapi/v1/?action=send_event" 799 | ``` 800 | 801 | *Example Response* 802 | ```javascript 803 | { 804 | "callid": "58312bb3398bc", 805 | "action": "send_event", 806 | "message": "ok" 807 | } 808 | ``` 809 | 810 | --- 811 | ### system_reboot 812 | - Just as it says, reboots the system. 813 | - HTTP: **GET** 814 | - Params: none 815 | 816 | *Example Request* 817 | ```bash 818 | curl \ 819 | -X GET \ 820 | --silent \ 821 | --insecure \ 822 | --header "fauxapi-auth: " \ 823 | "https:///fauxapi/v1/?action=system_reboot" 824 | ``` 825 | 826 | *Example Response* 827 | ```javascript 828 | { 829 | "callid": "58312bb3487ac", 830 | "action": "system_reboot", 831 | "message": "ok" 832 | } 833 | ``` 834 | 835 | --- 836 | ### system_stats 837 | - Returns various useful system stats. 838 | - HTTP: **GET** 839 | - Params: none 840 | 841 | *Example Request* 842 | ```bash 843 | curl \ 844 | -X GET \ 845 | --silent \ 846 | --insecure \ 847 | --header "fauxapi-auth: " \ 848 | "https:///fauxapi/v1/?action=system_stats" 849 | ``` 850 | 851 | *Example Response* 852 | ```javascript 853 | { 854 | "callid": "5b3b511655589", 855 | "action": "system_stats", 856 | "message": "ok", 857 | "data": { 858 | "stats": { 859 | "cpu": "20770421|20494981", 860 | "mem": "20", 861 | "uptime": "1 Day 21 Hours 25 Minutes 48 Seconds", 862 | "pfstate": "62/98000", 863 | "pfstatepercent": "0", 864 | "temp": "", 865 | "datetime": "20180703Z103358", 866 | "cpufreq": "", 867 | "load_average": [ 868 | "0.01", 869 | "0.04", 870 | "0.01" 871 | ], 872 | "mbuf": "1016/61600", 873 | "mbufpercent": "2" 874 | } 875 | } 876 | } 877 | ``` 878 | --- 879 | ### system_info 880 | - Returns various useful system info. 881 | - HTTP: **GET** 882 | - Params: none 883 | 884 | *Example Request* 885 | ```bash 886 | curl \ 887 | -X GET \ 888 | --silent \ 889 | --insecure \ 890 | --header "fauxapi-auth: " \ 891 | "https:///fauxapi/v1/?action=system_info" 892 | ``` 893 | 894 | *Example Response* 895 | ```javascript 896 | { 897 | "callid": "5e1d8ceb8ff47", 898 | "action": "system_info", 899 | "message": "ok", 900 | "data": { 901 | "info": { 902 | "sys": { 903 | "platform": { 904 | "name": "VMware", 905 | "descr": "VMware Virtual Machine" 906 | }, 907 | "serial_no": "", 908 | "device_id": "719e8c91c2c43b820400" 909 | }, 910 | "pfsense_version": { 911 | "product_version_string": "2.4.5-DEVELOPMENT", 912 | "product_version": "2.4.5-DEVELOPMENT", 913 | "product_version_patch": "0" 914 | }, 915 | "pfsense_remote_version": { 916 | "version": "2.4.5.a.20200112.1821", 917 | "installed_version": "2.4.5.a.20191218.2354", 918 | "pkg_version_compare": "<" 919 | }, 920 | "os_verison": "FreeBSD 11.3-STABLE", 921 | "cpu_type": { 922 | "cpu_model": "Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz", 923 | "cpu_count": "4", 924 | "logic_cpu_count": "4 package(s)", 925 | "cpu_freq": "" 926 | }, 927 | "kernel_pti_status": "enabled", 928 | "mds_mitigation": "inactive", 929 | "bios": { 930 | "vendor": "Phoenix Technologies LTD", 931 | "version": "6.00", 932 | "date": "07/29/2019" 933 | } 934 | } 935 | } 936 | } 937 | ``` 938 | --- 939 | 940 | ## Versions and Testing 941 | The FauxAPI has been developed against the following pfSense versions 942 | - **2.3.x** - 2.3.2, 2.3.3, 2.3.4, 2.3.5 943 | - **2.4.x** - 2.4.3, 2.4.4, 2.4.5 944 | - **2.5.x** - 2.5.0-DEVELOPMENT-amd64-20200527-1410 945 | 946 | FauxAPI has not been tested against 2.3.0 or 2.3.1. Additionally, it is apparent the pfSense 947 | packaging technique changed significantly prior to 2.3.x so it is unlikely it will be backported 948 | to anything prior to 2.3.0. 949 | 950 | Testing is reasonable but does not achieve 100% code coverage within the FauxAPI 951 | codebase. Two client side test scripts (1x Bash, 1x Python) that both 952 | demonstrate and test all possible server side actions are provided. Under the 953 | hood FauxAPI, performs real-time sanity checks and tests to make sure the user 954 | supplied configurations will save, load and reload as expected. 955 | 956 | __Shout Out:__ *Anyone that happens to know of _any_ test harness or test code 957 | for pfSense please get in touch - I'd very much prefer to integrate with existing 958 | pfSense test infrastructure if it already exists.* 959 | 960 | 961 | ## Releases 962 | #### v1.0 - 2016-11-20 963 | - initial release 964 | 965 | #### v1.1 - 2017-08-12 966 | - 2x new API actions **alias_update_urltables** and **gateway_status** 967 | - update documentation to address common points of confusion, especially the 968 | requirement to provide the _full_ config file not just the portion to be 969 | updated. 970 | - testing against pfSense 2.3.2 and 2.3.3 971 | 972 | #### v1.2 - 2017-08-27 973 | - new API action **function_call** allowing the user to reach deep into the inner 974 | code infrastructure of pfSense, this feature is intended for people with a 975 | solid understanding of PHP and pfSense. 976 | - the `credentials.ini` file now provides a way to control the permitted API 977 | actions. 978 | - various update documentation updates. 979 | - testing against pfSense 2.3.4 980 | 981 | #### v1.3 - 2018-07-02 982 | - add the **config_patch** function providing the ability to patch the system config, 983 | thus allowing API users to make granular configuration changes. 984 | - added a "previous_config_file" response attribute to functions that cause write 985 | operations to the running `config.xml` 986 | - add the **interface_stats** function to help in determining the usage of an 987 | interface to (partly) address [Issue #20](https://github.com/ndejong/pfsense_fauxapi/issues/20) 988 | - added a "number" attibute to the "rules" output making the actual rule number more 989 | explict as described in [Issue #13](https://github.com/ndejong/pfsense_fauxapi/issues/13) 990 | - addressed a bug with the **system_stats** function that was preventing it from 991 | returning, caused by an upstream change(s) in the pfSense code. 992 | - rename the confusing "owner" field in `credentials.ini` to "comment", legacy 993 | configuration files using "owner" are still supported. 994 | - added a "source" attribute to the logs making it easier to grep fauxapi events, 995 | for example `clog /var/log/system.log | grep fauxapi` 996 | - plenty of documentation fixes and updates 997 | - added documentation highlighting features and capabilities that existed but were not 998 | previously obvious 999 | - added the [`extras`](https://github.com/ndejong/pfsense_fauxapi/tree/master/extras) path 1000 | in the project repo as a better place to keep non-package files, `client-libs`, `examples`, 1001 | `build-tools` etc 1002 | - testing against pfSense 2.3.5 1003 | - testing against pfSense 2.4.3 1004 | 1005 | #### v1.4 - 2020-05-31 1006 | - Added **system_info** function to return various useful system information. 1007 | - include include `phpsessionmanager.inc` since it is commonly required in other function calls 1008 | - testing against pfSense 2.4.5 1009 | - testing against pfSense 2.5.0 (pfSense-CE-2.5.0-DEVELOPMENT-amd64-20200527-1410.iso) 1010 | 1011 | ## FauxAPI License 1012 | ``` 1013 | Copyright 2016-2020 Nicholas de Jong 1014 | 1015 | Licensed under the Apache License, Version 2.0 (the "License"); 1016 | you may not use this file except in compliance with the License. 1017 | You may obtain a copy of the License at 1018 | 1019 | http://www.apache.org/licenses/LICENSE-2.0 1020 | 1021 | Unless required by applicable law or agreed to in writing, software 1022 | distributed under the License is distributed on an "AS IS" BASIS, 1023 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1024 | See the License for the specific language governing permissions and 1025 | limitations under the License. 1026 | ``` 1027 | --------------------------------------------------------------------------------