├── .github └── workflows │ ├── linux.yml │ ├── openssl-1.0.2.yml │ └── openssl-1.1.1.yml ├── .gitignore ├── AUTHORS ├── Build.from-git ├── CMakeLists.txt ├── COPYING ├── ChangeLog ├── DISCUSS ├── Dockerfile ├── INSTALL ├── LICENSE ├── Makefile.am ├── NEWS ├── README ├── README.md ├── SECURITY.md ├── TODO.md ├── a2md.xml ├── buildconf ├── cmake └── modules │ ├── FindAPACHE.cmake │ ├── FindAPR.cmake │ └── FindJANSSON.cmake ├── configure.ac ├── contrib ├── README ├── md_events │ ├── dns_scripts │ │ ├── DNS_ROUTE53.md │ │ ├── GoDaddy-README.txt │ │ ├── README │ │ ├── dns_add_challtestsrv │ │ ├── dns_add_clouddns │ │ ├── dns_add_cloudflare │ │ ├── dns_add_dnspod │ │ ├── dns_add_duckdns │ │ ├── dns_add_godaddy │ │ ├── dns_add_joker │ │ ├── dns_add_lexicon │ │ ├── dns_add_linode │ │ ├── dns_add_manual │ │ ├── dns_add_nsupdate │ │ ├── dns_add_ovh │ │ ├── dns_add_pdns-mysql │ │ ├── dns_del_challtestsrv │ │ ├── dns_del_clouddns │ │ ├── dns_del_cloudflare │ │ ├── dns_del_dnspod │ │ ├── dns_del_duckdns │ │ ├── dns_del_godaddy │ │ ├── dns_del_joker │ │ ├── dns_del_lexicon │ │ ├── dns_del_linode │ │ ├── dns_del_manual │ │ ├── dns_del_nsupdate │ │ ├── dns_del_ovh │ │ ├── dns_del_pdns-mysql │ │ ├── dns_freedns.sh │ │ ├── dns_godaddy │ │ └── dns_route53.py │ └── md_events └── selinux │ ├── README │ ├── mod_md.fc │ ├── mod_md.if │ └── mod_md.te ├── docker-compose.yml ├── docker └── debian-sid │ ├── Dockerfile │ └── bin │ └── mod_md_test.sh ├── docs └── Testing.md ├── event_interface_notes.txt ├── m4 └── ax_check_compile_flag.m4 ├── mod_md_lib └── main.c ├── mod_md_ocsp_status.png ├── mod_md_status.png ├── patches ├── mod_ssl_md2-2.4.x.diff ├── mod_ssl_md2-trunk.diff └── mod_ssl_md3-dualcert.patch ├── scripts ├── contributors.sh ├── fix_le_pubcerts.sh └── md_message.sh ├── src ├── Makefile.am ├── md.h ├── md_acme.c ├── md_acme.h ├── md_acme_acct.c ├── md_acme_acct.h ├── md_acme_authz.c ├── md_acme_authz.h ├── md_acme_drive.c ├── md_acme_drive.h ├── md_acme_order.c ├── md_acme_order.h ├── md_acmev2_drive.c ├── md_acmev2_drive.h ├── md_cmd.h ├── md_cmd_acme.c ├── md_cmd_acme.h ├── md_cmd_main.c ├── md_cmd_reg.c ├── md_cmd_reg.h ├── md_cmd_store.c ├── md_cmd_store.h ├── md_core.c ├── md_crypt.c ├── md_crypt.h ├── md_curl.c ├── md_curl.h ├── md_event.c ├── md_event.h ├── md_http.c ├── md_http.h ├── md_json.c ├── md_json.h ├── md_jws.c ├── md_jws.h ├── md_log.c ├── md_log.h ├── md_ocsp.c ├── md_ocsp.h ├── md_reg.c ├── md_reg.h ├── md_result.c ├── md_result.h ├── md_status.c ├── md_status.h ├── md_store.c ├── md_store.h ├── md_store_fs.c ├── md_store_fs.h ├── md_tailscale.c ├── md_tailscale.h ├── md_time.c ├── md_time.h ├── md_util.c ├── md_util.h ├── md_version.h ├── md_version.h.in ├── mod_md.c ├── mod_md.h ├── mod_md_config.c ├── mod_md_config.h ├── mod_md_drive.c ├── mod_md_drive.h ├── mod_md_ocsp.c ├── mod_md_ocsp.h ├── mod_md_os.c ├── mod_md_os.h ├── mod_md_private.h ├── mod_md_status.c └── mod_md_status.h └── test ├── .gitignore ├── Makefile.am ├── modules └── md │ ├── __init__.py │ ├── conftest.py │ ├── data │ ├── sectigo-demo-root.pem │ ├── store_migrate │ │ └── 1.0 │ │ │ └── sample1 │ │ │ ├── accounts │ │ │ └── ACME-localhost-0000 │ │ │ │ ├── account.json │ │ │ │ └── account.pem │ │ │ ├── archive │ │ │ └── 7007-1502285564.org.1 │ │ │ │ └── md.json │ │ │ ├── domains │ │ │ └── 7007-1502285564.org │ │ │ │ ├── cert.pem │ │ │ │ ├── chain.pem │ │ │ │ ├── md.json │ │ │ │ └── pkey.pem │ │ │ ├── httpd.json │ │ │ └── md_store.json │ ├── test_920 │ │ └── 002.pubcert │ ├── test_conf_validate │ │ └── test_014.conf │ ├── test_drive │ │ └── test1.example.org.conf │ └── test_roundtrip │ │ └── temp.conf │ ├── dns01.py │ ├── dns01_v2.py │ ├── http_challenge_foobar.py │ ├── md_acme.py │ ├── md_cert_util.py │ ├── md_certs.py │ ├── md_conf.py │ ├── md_env.py │ ├── message.py │ ├── msg_fail_on.py │ ├── notifail.py │ ├── notify.py │ ├── pebble │ ├── pebble-eab.json.template │ └── pebble.json.template │ ├── test_001_store.py │ ├── test_010_store_migrate.py │ ├── test_100_reg_add.py │ ├── test_110_reg_update.py │ ├── test_120_reg_list.py │ ├── test_202_acmev2_regs.py │ ├── test_300_conf_validate.py │ ├── test_310_conf_store.py │ ├── test_502_acmev2_drive.py │ ├── test_602_roundtrip.py │ ├── test_702_auto.py │ ├── test_710_profiles.py │ ├── test_720_wildcard.py │ ├── test_730_static.py │ ├── test_740_acme_errors.py │ ├── test_741_setup_errors.py │ ├── test_750_eab.py │ ├── test_751_sectigo.py │ ├── test_752_zerossl.py │ ├── test_780_tailscale.py │ ├── test_790_failover.py │ ├── test_800_must_staple.py │ ├── test_801_stapling.py │ ├── test_810_ec.py │ ├── test_820_locks.py │ ├── test_900_notify.py │ ├── test_901_message.py │ ├── test_910_cleanups.py │ └── test_920_status.py ├── pyhttpd ├── __init__.py ├── certs.py ├── conf.py ├── conf │ ├── httpd.conf.template │ ├── mime.types │ ├── stop.conf.template │ └── test.conf ├── config.ini.in ├── curl.py ├── env.py ├── htdocs │ ├── alive.json │ ├── cgi │ │ ├── echo.py │ │ ├── echohd.py │ │ ├── env.py │ │ ├── files │ │ │ └── empty.txt │ │ ├── hecho.py │ │ ├── hello.py │ │ ├── mnot164.py │ │ ├── necho.py │ │ └── upload.py │ ├── forbidden.html │ ├── index.html │ ├── noh2 │ │ ├── alive.json │ │ └── index.html │ ├── test1 │ │ ├── 001.html │ │ ├── 002.jpg │ │ ├── 003.html │ │ ├── 003 │ │ │ └── 003_img.jpg │ │ ├── 004.html │ │ ├── 004 │ │ │ └── gophertiles.jpg │ │ ├── 006.html │ │ ├── 006 │ │ │ ├── 006.css │ │ │ ├── 006.js │ │ │ └── header.html │ │ ├── 007.html │ │ ├── 007 │ │ │ └── 007.py │ │ ├── 009.py │ │ ├── alive.json │ │ ├── apache.org-files │ │ │ ├── ant.jpg │ │ │ ├── asf_logo.png │ │ │ ├── async-ads.js │ │ │ ├── async-ads.js.br │ │ │ ├── cse.js │ │ │ ├── css.css │ │ │ ├── default.css │ │ │ ├── defaulten.css │ │ │ ├── defaulten.js │ │ │ ├── jquery-2.js │ │ │ ├── jsapi.js │ │ │ ├── min.css │ │ │ ├── min.css.br │ │ │ ├── mrunit.jpg │ │ │ ├── search_box_icon.png │ │ │ ├── small-logo.png │ │ │ ├── styles.css │ │ │ └── synapse.jpg │ │ ├── apache.org.html │ │ └── index.html │ └── test2 │ │ ├── 006 │ │ └── 006.css │ │ ├── 10%abnormal.txt │ │ ├── alive.json │ │ └── x%2f.test ├── log.py ├── mod_aptest │ └── mod_aptest.c ├── nghttp.py └── result.py ├── requirements.txt └── unit ├── main.c ├── test_common.h ├── test_md_json.c └── test_md_util.c /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Stefan Eissing (https://dev-icing.de) 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | name: Linux 17 | 18 | 'on': 19 | push: 20 | branches: 21 | - master 22 | - '*/ci' 23 | paths-ignore: 24 | - '**/*.md' 25 | pull_request: 26 | branches: 27 | - master 28 | paths-ignore: 29 | - '**/*.md' 30 | 31 | concurrency: 32 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 33 | cancel-in-progress: true 34 | 35 | permissions: {} 36 | 37 | env: 38 | MARGS: "-j5" 39 | CFLAGS: "-g" 40 | pebble-version: v2.7.0 41 | 42 | jobs: 43 | linux: 44 | name: ${{ matrix.name }} 45 | runs-on: ubuntu-latest 46 | timeout-minutes: 30 47 | strategy: 48 | fail-fast: false 49 | matrix: 50 | build: 51 | - name: Default 52 | install_packages: 53 | install_steps: pytest pebble 54 | 55 | steps: 56 | - name: 'install prereqs' 57 | run: | 58 | sudo apt-get update -y 59 | sudo apt-get install -y --no-install-suggests --no-install-recommends \ 60 | libtool autoconf automake pkgconf apache2 apache2-dev openssl \ 61 | curl nghttp2-client libssl-dev libjansson-dev libcurl4-openssl-dev \ 62 | ${{ matrix.build.install_packages }} 63 | python3 -m venv $HOME/venv 64 | 65 | - uses: actions/checkout@v4 66 | 67 | - name: 'install test prereqs' 68 | run: | 69 | [ -x "$HOME/venv/bin/activate" ] && source $HOME/venv/bin/activate 70 | python3 -m pip install -r test/requirements.txt 71 | 72 | - name: setup Go 73 | if: contains(matrix.build.install_steps, 'pebble') 74 | uses: actions/setup-go@v5 75 | 76 | - name: install pebble 77 | if: contains(matrix.build.install_steps, 'pebble') 78 | run: | 79 | export PATH=$PATH:$HOME/go/bin 80 | git clone --quiet --depth=1 -b ${{ env.pebble-version }} https://github.com/letsencrypt/pebble/ 81 | cd pebble 82 | go install ./cmd/pebble 83 | go install ./cmd/pebble-challtestsrv 84 | 85 | - name: 'configure' 86 | run: | 87 | export PATH=$PATH:$HOME/go/bin 88 | autoreconf -fi 89 | ./configure --enable-werror 90 | 91 | - name: 'build' 92 | run: make V=1 93 | 94 | - name: pytest 95 | if: contains(matrix.build.install_steps, 'pytest') 96 | env: 97 | PYTEST_ADDOPTS: "--color=yes" 98 | run: | 99 | export PATH=$PATH:$HOME/go/bin 100 | [ -x "$HOME/venv/bin/activate" ] && source $HOME/venv/bin/activate 101 | pytest -v 102 | -------------------------------------------------------------------------------- /.github/workflows/openssl-1.0.2.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Stefan Eissing (https://dev-icing.de) 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | name: OpenSSL-1.0.2 17 | 18 | 'on': 19 | push: 20 | branches: 21 | - master 22 | - '*/ci' 23 | paths-ignore: 24 | - '**/*.md' 25 | pull_request: 26 | branches: 27 | - master 28 | paths-ignore: 29 | - '**/*.md' 30 | 31 | concurrency: 32 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 33 | cancel-in-progress: true 34 | 35 | permissions: {} 36 | 37 | env: 38 | MARGS: "-j5" 39 | CFLAGS: "-g" 40 | 41 | jobs: 42 | linux: 43 | name: ${{ matrix.name }} 44 | runs-on: ubuntu-latest 45 | timeout-minutes: 30 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | build: 50 | - name: openssl 1.0.2 51 | install_packages: 52 | install_steps: 53 | 54 | steps: 55 | - name: 'install prereqs' 56 | run: | 57 | sudo apt-get update -y 58 | sudo apt-get install -y --no-install-suggests --no-install-recommends \ 59 | libtool autoconf automake pkgconf apache2 apache2-dev openssl \ 60 | curl nghttp2-client libssl-dev libjansson-dev libcurl4-openssl-dev \ 61 | ${{ matrix.build.install_packages }} 62 | python3 -m venv $HOME/venv 63 | 64 | - uses: actions/checkout@v4 65 | 66 | - name: 'install test prereqs' 67 | run: | 68 | [ -x "$HOME/venv/bin/activate" ] && source $HOME/venv/bin/activate 69 | python3 -m pip install -r test/requirements.txt 70 | 71 | - name: 'cache openssl' 72 | uses: actions/cache@v4 73 | id: cache-openssl 74 | env: 75 | cache-name: cache-openssl 76 | with: 77 | path: ~/openssl 78 | key: ${{ runner.os }}-build-${{ env.cache-name }}-1.0.2 79 | 80 | - name: 'install openssl' 81 | if: steps.cache-openssl.outputs.cache-hit != 'true' 82 | run: | 83 | curl -LO https://github.com/openssl/openssl/releases/download/OpenSSL_1_0_2/openssl-1.0.2.tar.gz 84 | tar xfz openssl-1.0.2.tar.gz 85 | cd openssl-1.0.2 86 | CFLAGS="-fPIC" ./config --prefix=$HOME/openssl --libdir=lib shared 87 | make 88 | make -j1 install_sw 89 | 90 | - name: 'configure' 91 | # configure without --enable-werror since openssl 1.0.2 will give warnings 92 | run: | 93 | autoreconf -fi 94 | ./configure --enable-werror --with-openssl=$HOME/openssl 95 | 96 | - name: 'build' 97 | run: make V=1 98 | 99 | -------------------------------------------------------------------------------- /.github/workflows/openssl-1.1.1.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Stefan Eissing (https://dev-icing.de) 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | name: OpenSSL-1.1.1 17 | 18 | 'on': 19 | push: 20 | branches: 21 | - master 22 | - '*/ci' 23 | paths-ignore: 24 | - '**/*.md' 25 | pull_request: 26 | branches: 27 | - master 28 | paths-ignore: 29 | - '**/*.md' 30 | 31 | concurrency: 32 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 33 | cancel-in-progress: true 34 | 35 | permissions: {} 36 | 37 | env: 38 | MARGS: "-j5" 39 | CFLAGS: "-g" 40 | 41 | jobs: 42 | linux: 43 | name: ${{ matrix.name }} 44 | runs-on: ubuntu-latest 45 | timeout-minutes: 30 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | build: 50 | - name: openssl 1.1.1 51 | install_packages: 52 | install_steps: 53 | 54 | steps: 55 | - name: 'install prereqs' 56 | run: | 57 | sudo apt-get update -y 58 | sudo apt-get install -y --no-install-suggests --no-install-recommends \ 59 | libtool autoconf automake pkgconf apache2 apache2-dev openssl \ 60 | curl nghttp2-client libssl-dev libjansson-dev libcurl4-openssl-dev \ 61 | ${{ matrix.build.install_packages }} 62 | python3 -m venv $HOME/venv 63 | 64 | - uses: actions/checkout@v4 65 | 66 | - name: 'install test prereqs' 67 | run: | 68 | [ -x "$HOME/venv/bin/activate" ] && source $HOME/venv/bin/activate 69 | python3 -m pip install -r test/requirements.txt 70 | 71 | - name: 'cache openssl' 72 | uses: actions/cache@v4 73 | id: cache-openssl 74 | env: 75 | cache-name: cache-openssl 76 | with: 77 | path: ~/openssl 78 | key: ${{ runner.os }}-build-${{ env.cache-name }}-1.1.1 79 | 80 | - name: 'install openssl' 81 | if: steps.cache-openssl.outputs.cache-hit != 'true' 82 | run: | 83 | curl -LO https://github.com/openssl/openssl/releases/download/OpenSSL_1_1_1s/openssl-1.1.1s.tar.gz 84 | tar xfz openssl-1.1.1s.tar.gz 85 | cd openssl-1.1.1s 86 | CFLAGS="-fPIC" ./config --prefix=$HOME/openssl --libdir=lib shared 87 | make 88 | make -j1 install_sw 89 | 90 | - name: 'configure' 91 | # configure without --enable-werror since openssl 1.1.1 will give warnings 92 | run: | 93 | autoreconf -fi 94 | ./configure --enable-werror --with-openssl=$HOME/openssl 95 | 96 | - name: 'build' 97 | run: make V=1 98 | 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | mod_md.xcodeproj/xcuserdata/*.xcuserdatad 2 | *.xcuserstate 3 | *.o 4 | *.slo 5 | *.lo 6 | *.la 7 | *.pcap 8 | *~ 9 | .libs 10 | .configured 11 | .deps 12 | .dirstamp 13 | .pytest_cache 14 | compile 15 | aclocal.m4 16 | autom4te.cache 17 | autoscan.log 18 | config.guess 19 | config.log 20 | config.status 21 | config.sub 22 | config.h 23 | config.h.in 24 | config.h.in~ 25 | config.cache 26 | configure 27 | configure.scan 28 | depcomp 29 | httpd.conf 30 | install-sh 31 | libtool 32 | ltmain.sh 33 | missing 34 | stamp-h1 35 | Makefile.in 36 | Makefile 37 | mod_md-*.tar.gz 38 | m4 39 | .cache 40 | src/a2md 41 | a2md.1 42 | 43 | # Automake test-suite artifacts 44 | /test-driver 45 | /test/test-suite.log 46 | /test/unit/**/*.log 47 | /test/unit/**/*.trs 48 | /test/unit/main 49 | wiki 50 | test/conf/std_vhosts.conf 51 | build/ 52 | .idea 53 | a2md.1 54 | README.md.bak 55 | .env 56 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Stefan Eissing 2 | Tests by Michael Köller 3 | -------------------------------------------------------------------------------- /Build.from-git: -------------------------------------------------------------------------------- 1 | Building mod_md from the git repository 2 | 3 | You can build in Docker using Dockerfile in the git repository. 4 | 5 | If you don't have/don't want to use Docker, the following procedure 6 | will build it outside docker. 7 | 8 | Prerequisites: 9 | o Assumes you have built httpd from source 10 | o Install libjansson 11 | - From your distro - libjansson-dev 12 | - From source: http://www.digip.org/jansson 13 | o Install libcurl-dev (e.g libcurl4-openssl-dev, or https://curl.haxx.se/download.html) 14 | 15 | Procedure: 16 | 17 | git clone https://github.com/icing/mod_md.git 18 | 19 | autoreconf -i 20 | automake 21 | autoconf 22 | ./configure -C --with-apxs=/usr/local/bin/apxs 23 | make 24 | make install 25 | 26 | To use this mod_md, you need to enable it in some 27 | distribution-specific manner. 28 | 29 | For bare httpd, your configuration should include: 30 | 31 | LoadModule ssl_module modules/mod_ssl.so 32 | LoadModule watchdog_module /usr/local/lib/httpd/modules/mod_watchdog.so 33 | LoadModule md_module /usr/local/lib/httpd/modules/mod_md.so 34 | 35 | See the mod_md README for details on configuring mod_md. 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Contributors to mod_md project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | PROJECT(mod_md C) 16 | CMAKE_MINIMUM_REQUIRED(VERSION 3.14) 17 | 18 | INCLUDE(CheckSymbolExists) 19 | 20 | IF(WIN32) 21 | SET(CMAKE_C_FLAGS "/O2 /MD /W3 /Zi") 22 | # TODO: Questionable...people might want to override -DCMAKE_C_FLAGS_DEBUG="..." 23 | SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS}") 24 | SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS}") 25 | ELSE() 26 | 27 | ENDIF() 28 | 29 | SET(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) 30 | 31 | SET(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/modules) 32 | 33 | FIND_PACKAGE(OpenSSL REQUIRED) 34 | FIND_PACKAGE(CURL REQUIRED) 35 | FIND_PACKAGE(JANSSON REQUIRED) 36 | FIND_PACKAGE(APACHE REQUIRED) 37 | FIND_PACKAGE(APR REQUIRED) 38 | 39 | INCLUDE_DIRECTORIES(${APR_INCLUDE_DIR}) 40 | INCLUDE_DIRECTORIES(${APRUTIL_INCLUDE_DIR}) 41 | INCLUDE_DIRECTORIES(${APACHE_INCLUDE_DIR}) 42 | INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) 43 | INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR}) 44 | INCLUDE_DIRECTORIES(${JANSSON_INCLUDE_DIR}) 45 | 46 | AUX_SOURCE_DIRECTORY(${CMAKE_SOURCE_DIR}/src SRC_LIST) 47 | ADD_LIBRARY(mod_md MODULE ${SRC_LIST}) 48 | SET_TARGET_PROPERTIES(mod_md PROPERTIES PREFIX "") 49 | SET_TARGET_PROPERTIES(mod_md PROPERTIES SUFFIX ".so") 50 | 51 | TARGET_LINK_LIBRARIES(mod_md ${APR_LIBRARIES} ${APRUTIL_LIBRARIES} ${APACHE_LIBRARY} ${OPENSSL_LIBRARIES} ${CURL_LIBRARIES} ${JANSSON_LIBRARIES}) 52 | 53 | MESSAGE(STATUS "") 54 | MESSAGE(STATUS "") 55 | MESSAGE(STATUS "mod_md configuration summary:") 56 | MESSAGE(STATUS "") 57 | MESSAGE(STATUS " Build type ...................... : ${CMAKE_BUILD_TYPE}") 58 | MESSAGE(STATUS " Install prefix .................. : ${CMAKE_INSTALL_PREFIX}") 59 | MESSAGE(STATUS " C compiler ...................... : ${CMAKE_C_COMPILER}") 60 | MESSAGE(STATUS " APR include directory ........... : ${APR_INCLUDE_DIR}") 61 | MESSAGE(STATUS " APR libraries ................... : ${APR_LIBRARIES}") 62 | MESSAGE(STATUS " APR Util include directory ...... : ${APRUTIL_INCLUDE_DIR}") 63 | MESSAGE(STATUS " APR Util libraries .............. : ${APRUTIL_LIBRARIES}") 64 | MESSAGE(STATUS " OpenSSL include directory ....... : ${OPENSSL_INCLUDE_DIR}") 65 | MESSAGE(STATUS " OpenSSL libraries ............... : ${OPENSSL_LIBRARIES}") 66 | MESSAGE(STATUS " Curl include directory........... : ${CURL_INCLUDE_DIR}") 67 | MESSAGE(STATUS " Curl libraries................... : ${CURL_LIBRARIES}") 68 | MESSAGE(STATUS " Jansson include directory.........: ${JANSSON_INCLUDE_DIR}") 69 | MESSAGE(STATUS " Jansson libraries ............... : ${JANSSON_LIBRARIES}") 70 | MESSAGE(STATUS "") 71 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | Please see the file called LICENSE. 3 | -------------------------------------------------------------------------------- /DISCUSS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/DISCUSS -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:xenial 2 | 3 | RUN apt update 4 | RUN apt install -y software-properties-common 5 | ENV LC_ALL C.UTF-8 6 | RUN add-apt-repository ppa:ondrej/apache2 7 | RUN apt update 8 | RUN apt install -y apache2 apache2-dev build-essential autoconf make libtool libssl-dev libjansson-dev libcurl4-openssl-dev 9 | 10 | COPY . mod_md 11 | WORKDIR mod_md 12 | 13 | RUN autoreconf -i 14 | RUN automake 15 | RUN autoconf 16 | RUN ./configure --with-apxs=/usr/bin/apxs 17 | RUN make 18 | RUN make install 19 | 20 | RUN echo >/etc/apache2/mods-available/md.load "LoadModule md_module /usr/lib/apache2/modules/mod_md.so" 21 | RUN ln -s ../mods-available/md.load /etc/apache2/mods-enabled/md.load 22 | 23 | VOLUME /etc/apache 24 | WORKDIR /etc/apache 25 | 26 | CMD ["/usr/sbin/apache2", "-d", ".", "-DFOREGROUND"] 27 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | # Copyright 2019 greenbytes GmbH (https://www.greenbytes.de) 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | SUBDIRS = @BUILD_SUBDIRS@ 16 | DIST_SUBDIRS = src test 17 | 18 | ACLOCAL_AMFLAGS = -I m4 19 | 20 | dist_doc_DATA = README README.md LICENSE 21 | EXTRA_DIST = patches a2md.xml 22 | 23 | if BUILD_MANPAGES 24 | man1_MANS = a2md.1 25 | 26 | a2md.1: $(srcdir)/a2md.xml 27 | rm -f a2md.1 28 | $(XMLTO) --skip-validation man $(srcdir)/a2md.xml 29 | 30 | endif 31 | 32 | .PHONY: test 33 | 34 | test: all-recursive 35 | $(MAKE) -C test/ test 36 | 37 | docker-test: 38 | docker-compose build debian-sid 39 | docker-compose run debian-sid 40 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/NEWS -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | See README.md -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | This repository lives on the current, released version. 6 | 7 | If you look at a more long term perspective, the recent 8 | versions regularly become part of Apache httpd and are 9 | supported via that release line. 10 | 11 | ## Reporting a Vulnerability 12 | 13 | Security relevant reports are best made to security@httpd.apache.org 14 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | See [github issues](https://github.com/icing/mod_md/issues). 3 | -------------------------------------------------------------------------------- /buildconf: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # setup/re-init the autoconf files on a raw checkout 4 | # minimalistic version 5 | # 6 | 7 | fail() { 8 | echo "FAIL: $@" >&2 9 | exit 1 10 | } 11 | 12 | # check location 13 | 14 | if [ ! -f ./configure.ac ]; then 15 | fail "$0 needs to run fromt the top level directory where configure.ac resides." 16 | fi 17 | 18 | # check tools we need 19 | 20 | AUTOCONF="${AUTOCONF:-autoconf}" 21 | AUTORECONF="${AUTORECONF:-autoreconf}" 22 | AUTOMAKE="${AUTOMAKE:-automake}" 23 | 24 | for tool in "$AUTOCONF" "$AUTORECONF" "$AUTOMAKE"; do 25 | type "$tool" 2>&1 >/dev/null 26 | if test $? -ne 0; then 27 | fail "need ${tool} installed." 28 | fi 29 | done 30 | 31 | for i in .configured .deps compile aclocal.m4 autom4te.cache \ 32 | autoscan.log config.guess config.status config.sub \ 33 | config.h config.h.in config.h.in~ configure configure.scan \ 34 | depcomp install-sh libtool ltmain.sh missing stamp-h1 \ 35 | Makefile.in Makefile \ 36 | src/Makefile.in src/Makefile \ 37 | test/Makefile.in test/Makefile \ 38 | ; do 39 | test -z "$i" || rm -rf "$i" 40 | done 41 | 42 | "$AUTORECONF" -i || exit $? 43 | "$AUTOMAKE" || exit $? 44 | "$AUTOCONF" || exit $? 45 | -------------------------------------------------------------------------------- /cmake/modules/FindAPACHE.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Contributors to mod_md project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # One must set a hint: APACHE_ROOT_DIR 16 | # Module defines 17 | # APACHE_FOUND - System has APACHE 18 | # APACHE_INCLUDE_DIR - The APACHE include directory 19 | # APACHE_LIBRARIES - Library 20 | # Defined: 21 | # APACHE_LIBRARY - httpd lib 22 | 23 | IF(NOT APACHE_ROOT_DIR) 24 | message(FATAL_ERROR "APACHE_ROOT_DIR is not set. We don't know where to look for APACHE, quitting.") 25 | ENDIF() 26 | FIND_PATH(APACHE_INCLUDE_DIR httpd.h 27 | "${APACHE_ROOT_DIR}/include" 28 | ) 29 | SET(APACHE_NAMES ${APACHE_NAMES} httpd libhttpd) 30 | FIND_LIBRARY(APACHE_LIBRARY NAMES ${APACHE_NAMES} PATHS "${APACHE_ROOT_DIR}/lib" "${APACHE_ROOT_DIR}/lib64") 31 | IF (APACHE_LIBRARY AND APACHE_INCLUDE_DIR) 32 | SET(APACHE_LIBRARIES ${APACHE_LIBRARY}) 33 | SET(APACHE_FOUND "YES") 34 | ELSE (APACHE_LIBRARY AND APACHE_INCLUDE_DIR) 35 | SET(APACHE_FOUND "NO") 36 | ENDIF (APACHE_LIBRARY AND APACHE_INCLUDE_DIR) 37 | 38 | IF (APACHE_FOUND) 39 | IF (NOT APACHE_FIND_QUIETLY) 40 | MESSAGE(STATUS "Found APACHE: ${APACHE_LIBRARIES}") 41 | ENDIF (NOT APACHE_FIND_QUIETLY) 42 | ELSE (APACHE_FOUND) 43 | IF (APACHE_FIND_REQUIRED) 44 | MESSAGE(FATAL_ERROR "Could not find APACHE library") 45 | ENDIF (APACHE_FIND_REQUIRED) 46 | ENDIF (APACHE_FOUND) 47 | 48 | MARK_AS_ADVANCED( 49 | APACHE_LIBRARY 50 | APACHE_INCLUDE_DIR 51 | ) -------------------------------------------------------------------------------- /cmake/modules/FindAPR.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Contributors to mod_md project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # One must set a hint: APR_ROOT_DIR, APRUTIL_ROOT_DIR 16 | # Find the APR includes and libraries 17 | # Module defines 18 | # APR_INCLUDE_DIR and APRUTIL_INCLUDE_DIR, apr.h, etc. 19 | # APR_LIBRARIES and APRUTIL_LIBRARIES, libs needed to use APR. 20 | # APR_FOUND and APRUTIL_FOUND, whether APR usable. 21 | # Defined: 22 | # APR_LIBRARY and APRUTIL_LIBRARY, where to find the APR library. 23 | 24 | IF(NOT APR_ROOT_DIR) 25 | message(FATAL_ERROR "APR_ROOT_DIR is not set. We don't know where to look for APR, quitting.") 26 | ENDIF() 27 | 28 | IF(NOT APRUTIL_ROOT_DIR) 29 | message(FATAL_ERROR "APR_ROOT_DIR is not set. We don't know where to look for APR, quitting.") 30 | ENDIF() 31 | 32 | FIND_PATH(APR_INCLUDE_DIR apr.h 33 | "${APR_ROOT_DIR}/include" 34 | ) 35 | SET(APR_NAMES ${APR_NAMES} libapr-1 apr-1) 36 | FIND_LIBRARY(APR_LIBRARY NAMES ${APR_NAMES} PATHS "${APR_ROOT_DIR}/lib" "${APR_ROOT_DIR}/lib64") 37 | IF (APR_LIBRARY AND APR_INCLUDE_DIR) 38 | SET(APR_LIBRARIES ${APR_LIBRARY}) 39 | SET(APR_FOUND "YES") 40 | ELSE (APR_LIBRARY AND APR_INCLUDE_DIR) 41 | SET(APR_FOUND "NO") 42 | ENDIF (APR_LIBRARY AND APR_INCLUDE_DIR) 43 | 44 | IF (APR_FOUND) 45 | IF (NOT APR_FIND_QUIETLY) 46 | MESSAGE(STATUS "Found APR: ${APR_LIBRARIES}") 47 | ENDIF (NOT APR_FIND_QUIETLY) 48 | ELSE (APR_FOUND) 49 | IF (APR_FIND_REQUIRED) 50 | MESSAGE(FATAL_ERROR "Could not find APR library") 51 | ENDIF (APR_FIND_REQUIRED) 52 | ENDIF (APR_FOUND) 53 | 54 | MARK_AS_ADVANCED( 55 | APR_LIBRARY 56 | APR_INCLUDE_DIR 57 | ) 58 | 59 | # APR Util 60 | FIND_PATH(APRUTIL_INCLUDE_DIR apu.h 61 | "${APRUTIL_ROOT_DIR}/include" 62 | ) 63 | 64 | SET(APRUTIL_NAMES ${APRUTIL_NAMES} libaprutil-1 aprutil-1) 65 | FIND_LIBRARY(APRUTIL_LIBRARY 66 | NAMES ${APRUTIL_NAMES} 67 | PATHS "${APRUTIL_ROOT_DIR}/lib" "${APRUTIL_ROOT_DIR}/lib64" 68 | ) 69 | 70 | IF (APRUTIL_LIBRARY AND APRUTIL_INCLUDE_DIR) 71 | SET(APRUTIL_LIBRARIES ${APRUTIL_LIBRARY}) 72 | SET(APRUTIL_FOUND "YES") 73 | ELSE (APRUTIL_LIBRARY AND APRUTIL_INCLUDE_DIR) 74 | SET(APRUTIL_FOUND "NO") 75 | ENDIF (APRUTIL_LIBRARY AND APRUTIL_INCLUDE_DIR) 76 | 77 | IF (APRUTIL_FOUND) 78 | IF (NOT APRUTIL_FIND_QUIETLY) 79 | MESSAGE(STATUS "Found APRUTIL: ${APRUTIL_LIBRARIES}") 80 | ENDIF (NOT APRUTIL_FIND_QUIETLY) 81 | ELSE (APRUTIL_FOUND) 82 | IF (APRUTIL_FIND_REQUIRED) 83 | MESSAGE(FATAL_ERROR "Could not find APRUTIL library") 84 | ENDIF (APRUTIL_FIND_REQUIRED) 85 | ENDIF (APRUTIL_FOUND) 86 | 87 | MARK_AS_ADVANCED( 88 | APRUTIL_LIBRARY 89 | APRUTIL_INCLUDE_DIR 90 | ) 91 | -------------------------------------------------------------------------------- /cmake/modules/FindJANSSON.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Contributors to mod_md project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # One must set a hint: JANSSON_ROOT_DIR 16 | # Once done this will define 17 | # JANSSON_FOUND 18 | # JANSSON_INCLUDE_DIRS 19 | # JANSSON_LIBRARIES 20 | 21 | IF(NOT JANSSON_ROOT_DIR) 22 | message(FATAL_ERROR "JANSSON_ROOT_DIR is not set. We don't know where to look for JANSSON, quitting.") 23 | ENDIF() 24 | 25 | 26 | FIND_PATH(JANSSON_INCLUDE_DIR jansson.h 27 | "${JANSSON_ROOT_DIR}/include" 28 | ) 29 | 30 | SET(JANSSON_NAMES ${JANSSON_NAMES} jansson libjansson) 31 | FIND_LIBRARY(JANSSON_LIBRARY NAMES ${JANSSON_NAMES} PATHS "${JANSSON_ROOT_DIR}/lib" "${JANSSON_ROOT_DIR}/lib64") 32 | 33 | IF (JANSSON_LIBRARY AND JANSSON_INCLUDE_DIR) 34 | SET(JANSSON_LIBRARIES ${JANSSON_LIBRARY}) 35 | SET(JANSSON_FOUND "YES") 36 | ELSE (JANSSON_LIBRARY AND JANSSON_INCLUDE_DIR) 37 | SET(JANSSON_FOUND "NO") 38 | ENDIF (JANSSON_LIBRARY AND JANSSON_INCLUDE_DIR) 39 | 40 | IF (JANSSON_FOUND) 41 | IF (NOT JANSSON_FIND_QUIETLY) 42 | MESSAGE(STATUS "Found JANSSON: ${JANSSON_LIBRARIES}") 43 | ENDIF (NOT JANSSON_FIND_QUIETLY) 44 | ELSE (JANSSON_FOUND) 45 | IF (JANSSON_FIND_REQUIRED) 46 | MESSAGE(FATAL_ERROR "Could not find JANSSON library") 47 | ENDIF (JANSSON_FIND_REQUIRED) 48 | ENDIF (JANSSON_FOUND) 49 | 50 | MARK_AS_ADVANCED( 51 | JANSSON_LIBRARY 52 | JANSSON_INCLUDE_DIR 53 | ) 54 | -------------------------------------------------------------------------------- /contrib/README: -------------------------------------------------------------------------------- 1 | This directory contains contributed code that may be useful in 2 | conjunction with mod_md. Any support is from the community. 3 | 4 | 5 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/DNS_ROUTE53.md: -------------------------------------------------------------------------------- 1 | # Do DNS-01 verification using Route53 2 | 3 | I was not about to implement this in BASH, sorry guys. I'd like you to have it, however. 4 | 5 | It's pretty simple to use. 6 | 7 | 1. pip install boto3 dnspython 8 | 2. ln -s dns_route53.py dns_add_route53 9 | 3. ln -s dns_route53.py dns_del_route53 10 | 4. Use it just like the other scripts 11 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/GoDaddy-README.txt: -------------------------------------------------------------------------------- 1 | Using GoDaddy DNS for LetsEncrypt domain validation. 2 | 3 | Quick guide to setting up getssl for domain validation of 4 | GoDaddy DNS domains. 5 | 6 | There are two prerequisites to using getssl with GoDaddy DNS: 7 | 8 | 1) Obtain an API access key from developer.godaddy.com 9 | At first sign-up, you will be required to take a "test" key. 10 | This is NOT what you need. Accept it, then get a "Production" 11 | key. At this writing, there is no charge - but you must have 12 | a GoDaddy customer account. 13 | 14 | You must get the API key for the account which owns the domain 15 | that you want to get certificates for. If the domains that you 16 | manage are owned by more than one account, get a key for each. 17 | 18 | The access key consists of a "Key" and a "Secret". You need 19 | both. 20 | 21 | 2) Obtain JSON.sh - https://github.com/dominictarr/JSON.sh 22 | 23 | With those in hand, the installation procedure is: 24 | 25 | 1) Put JSON.sh in the getssl DNS scripts directory 26 | Default: /usr/share/getssl/dns_scripts 27 | 28 | 2) Open your config file (the global file in ~/.getssl/getssl.cfg 29 | or the per-account file in ~/.getssl/example.net/getssl.cfg 30 | 31 | 3) Set the following options: 32 | VALIDATE_VIA_DNS="true" 33 | DNS_ADD_COMMAND="/usr/share/getssl/dns_scripts/dns_add_godaddy" 34 | DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_godaddy" 35 | # The API key for your account/this domain 36 | export GODADDY_KEY="..." GODADDY_SECRET="..." 37 | 38 | 4) Set any other options that you wish (per the standard 39 | directions.) Use the test CA to make sure that 40 | everything is setup correctly. 41 | 42 | That's it. getssl example.net will now validate with DNS. 43 | 44 | To trace record additions and removals, run getssl as 45 | GODADDY_TRACE=Y getssl example.net 46 | 47 | There are additional options, which are documented in the 48 | *godaddy" files and dns_godaddy -h. 49 | 50 | Copyright (2017) Timothe Litt litt at acm _dot org 51 | 52 | This sofware may be freely used providing this notice is included with 53 | all copies. The name of the author may not be used to endorse 54 | any other product or derivative work. No warranty is provided 55 | and the user assumes all responsibility for use of this software. 56 | 57 | Report any issues to https://github.com/tlhackque/getssl/issues. 58 | 59 | Enjoy. 60 | 61 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/README: -------------------------------------------------------------------------------- 1 | This directory is a snapshot of https://github.com/srvrco/getssl/dns_scripts 2 | 3 | It is provided AS-IS, subject to the licensing of https://github.com/srvrco/getssl. 4 | 5 | An interface is provided that MAY work with these scripts to install dns-01 challenge 6 | responses. It is highly experimental at this time. After installing md_events, read 7 | the example.net.conf description for details. 8 | 9 | While not supported, reports of success (or even failure) are welcome. 10 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_add_challtestsrv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Simple script to update the challtestserv mock DNS server when testing DNS responses 3 | 4 | fulldomain="${1}" 5 | token="${2}" 6 | 7 | curl -X POST -d "{\"host\":\"_acme-challenge.${fulldomain}.\", \"value\": \"${token}\"}" http://10.30.50.3:8055/set-txt 8 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_add_dnspod: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # need to add your email address and key to dnspod below 4 | key=${DNSPOD_API_KEY:-} 5 | 6 | fulldomain="$1" 7 | token="$2" 8 | 9 | NumParts=$(echo "$fulldomain" | awk -F"." '{print NF}') 10 | if [[ $NumParts -gt 2 ]]; then 11 | domain=$(echo "$fulldomain" | awk -F\. '{print $(NF-1) FS $NF}') 12 | txtname="_acme-challenge$(echo "$fulldomain" | awk -F\. '{for (i=1; i/dev/null | grep Auth-Sid | awk '{ print $2 }') 27 | 28 | ## put zone data in tempfile 29 | curl --silent -X POST https://dmapi.joker.com/request/dns-zone-get \ 30 | -H "Accept: application/json" -H "User-Agent: getssl/0.1" \ 31 | -H "application/x-www-form-urlencoded" -d "domain=${DOMAIN_ROOT}&auth-sid=${SID}" | \ 32 | tail -n +7 >"${TMPFILE}" 33 | 34 | ## add txt record 35 | printf "_acme-challenge.%s. TXT 0 \"%s \" 300\n\n" "${FULLDOMAIN}" "${TOKEN}" >>"${TMPFILE}" 36 | 37 | ## generate encoded url data 38 | URLDATA=$(cat "${TMPFILE}" | sed 's/ /%20/g' | sed 's/"/%22/g' | sed ':a;N;$!ba;s/\n/%0A/g') 39 | 40 | ## write new zonefile to joker 41 | curl --silent --output /dev/null "https://dmapi.joker.com/request/dns-zone-put?domain=${DOMAIN_ROOT}&zone=${URLDATA}&auth-sid=${SID}" 2>&1 42 | 43 | ## remove tempfile 44 | rm -f "${TMPFILE}" 45 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_add_lexicon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # a simple wrapper for Lexicon - https://github.com/AnalogJ/lexicon - a python script which can 4 | # Manipulate DNS records on various DNS providers in a standardized way. 5 | # You need to define the following environmental variables 6 | # LEXICON_PROVIDER 7 | # Every DNS service and auth flag maps to an Environmental Variable as follows: LEXICON_{DNS Provider Name}_{Auth Type} 8 | # eg LEXICON_CLOUDFLARE_USERNAME and LEXICON_CLOUDFLARE_TOKEN or LEXICON_DIGITALOCEAN_TOKEN 9 | 10 | fulldomain="${1}" 11 | token="${2}" 12 | 13 | lexicon "$LEXICON_PROVIDER" \ 14 | create "$fulldomain" TXT \ 15 | --name="_acme-challenge.${fulldomain}." \ 16 | --content="$token" 17 | 18 | exit 19 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_add_linode: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | fulldomain="${1}" 4 | token="${2}" 5 | api_url="https://api.linode.com/api/" 6 | api_key=${LINODE_KEY:-''} 7 | 8 | # Verify that required parameters are set 9 | if [[ -z "$fulldomain" ]]; then 10 | echo "DNS script requires full domain name as first parameter" 11 | exit 1 12 | fi 13 | if [[ -z "$token" ]]; then 14 | echo "DNS script requires challenge token as second parameter" 15 | exit 1 16 | fi 17 | if [[ -z "$LINODE_KEY" ]]; then 18 | echo "LINODE_KEY variable not set" 19 | exit 1 20 | fi 21 | 22 | domain_root=$(echo "$fulldomain" | awk -F\. '{print $(NF-1) FS $NF}') 23 | domain=${fulldomain%.$domain_root} 24 | txtname="_acme-challenge.$domain" 25 | 26 | # Get Domain ID 27 | response=$(curl --silent -X POST "$api_url" \ 28 | -H "Accept: application/json" -H "User-Agent: getssl/0.1" -H "application/x-www-form-urlencoded" \ 29 | -d "api_key=${api_key}&api_action=domain.list" ) 30 | domain_id=$(echo "$response" | egrep -o "{\"DOMAIN\":\"$domain_root\".*\"DOMAINID\":([0-9]+)" | egrep -o "[0-9]+$") 31 | if [[ $domain_id == "" ]]; then 32 | echo "Failed to fetch DomainID" 33 | exit 1 34 | fi 35 | 36 | # Create TXT record 37 | response=$(curl --silent -X POST "$api_url" \ 38 | -H "Accept: application/json" -H "User-Agent: getssl/0.1" -H "application/x-www-form-urlencoded" \ 39 | -d "api_key=$api_key&api_action=domain.resource.create&DomainID=$domain_id&Type=TXT&Name=$txtname&Target=$token" ) 40 | errors=$(echo "$response" | egrep -o "\"ERRORARRAY\":\[.*\]") 41 | if [[ $errors != "\"ERRORARRAY\":[]" ]]; then 42 | echo "Something went wrong: $errors" 43 | exit 1 44 | fi 45 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_add_manual: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "In the DNS, a new TXT record needs to be created for;" 4 | echo "_acme-challenge.${1}" 5 | echo "containing the following value" 6 | echo "$2" 7 | 8 | read -r -p "Press any key to obtain the certificate once the records have been updated..." 9 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_add_nsupdate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # example of script to add token to local dns using nsupdate 4 | 5 | fulldomain="$1" 6 | token="$2" 7 | 8 | # VARIABLES: 9 | # 10 | # DNS_NSUPDATE_KEYFILE - path to a TSIG key file, if required 11 | # DNS_NSUPDATE_GETKEY - command to execute if access to the key file requires 12 | # some special action: mounting a disk, decrypting a file.. 13 | # Called with the operation 'add' and action 'open" / 'close' 14 | 15 | 16 | if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then 17 | if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'open' "${fulldomain}" ; then 18 | exit $(( $? + 128 )) 19 | fi 20 | 21 | options="-k ${DNS_NSUPDATE_KEYFILE}" 22 | fi 23 | 24 | if [ -n "${DNS_SERVER}" ]; then 25 | cmd+="server ${DNS_SERVER}\n" 26 | fi 27 | 28 | cmd+="update add ${DNS_ZONE:-"_acme-challenge.${fulldomain}."} 300 in TXT \"${token}\"\n" 29 | cmd+="\n" # blank line is a "send" command to nsupdate 30 | 31 | printf "$cmd" | nsupdate ${options} -v 32 | 33 | sts=$? 34 | 35 | if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then 36 | if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'close' "${fulldomain}"; then 37 | exit $(( sts + ( $? * 10 ) )) 38 | fi 39 | fi 40 | 41 | exit ${sts} 42 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_add_ovh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domains=($(echo "$1"|sed -e 's/^\(\([a-zA-Z0-9.-]*\?\)\.\)*\([a-zA-Z0-9-]\+\.[a-zA-Z-]\+\)$/"\1" _acme-challenge.\2 \3/g')) 4 | challenge="$2" 5 | 6 | # Please, do not forget to ask for your credentials at https://eu.api.ovh.com/createToken/ 7 | # permissions needed are /domain/zone/* in GET,POST,DELETE 8 | applicationKey="YourAK" 9 | applicationSecret="YourAS" 10 | consumerKey="YourCK" 11 | 12 | topDomain=${domains[2]} 13 | subDomain=${domains[1]%%.} 14 | 15 | function send 16 | { 17 | method=$1 18 | url=$2 19 | body=$3 20 | ts=$(date +%s) 21 | 22 | sign=\$1\$$(echo -n "${applicationSecret}+${consumerKey}+${method}+https://eu.api.ovh.com/1.0${url}+${body}+${ts}"|sha1sum|cut -d" " -f1) 23 | curl -X "${method}" -H "Content-Type: application/json" -H "X-Ovh-Application: ${applicationKey}" -H "X-Ovh-Timestamp: ${ts}" -H "X-Ovh-Signature: ${sign}" -H "X-Ovh-Consumer: ${consumerKey}" -d "${body}" "https://eu.api.ovh.com/1.0${url}" 24 | } 25 | 26 | # Creation request 27 | send POST "/domain/zone/${topDomain}/record" "{\"fieldType\":\"TXT\",\"subDomain\":\"$subDomain\",\"ttl\":60,\"target\":\"$challenge\"}" 28 | 29 | # Refresh request 30 | send POST "/domain/zone/${topDomain}/refresh" "" 31 | 32 | # Pause for 10 seconds, for DNS propagation 33 | sleep 10 34 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_add_pdns-mysql: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # You must either have a suitable ~/.my.cnf containing a user / pass 4 | # for your mysql / mariadb database, OR you must uncomment the next line 5 | # (which is a security risk; don't do it!) and adjust accordingly. 6 | 7 | #CREDENTIALS="-uUSERNAME -pPASSWORD" 8 | 9 | FQDN=$1 10 | TOKEN=$2 11 | 12 | # If your database name is not powerdns, change it here. 13 | DB="powerdns" 14 | 15 | DOMAIN=${FQDN} 16 | 17 | # Iterate over the database, checking for a match. Keep stripping 18 | # subdomains off 1 by 1 until we find one, or exit with an error. 19 | while [[ -z "${DOMAIN_ID}" ]]; do 20 | DOMAIN_ID=$(mysql -ss "${CREDENTIALS}" -e "SELECT id FROM ${DB}.domains WHERE name='${DOMAIN}'") 21 | if [[ -z "${DOMAIN_ID}" ]]; then 22 | DOMAIN="$(echo "${DOMAIN}"|cut -d. -f1 --complement)" 23 | fi 24 | if [[ ${DOMAIN} != *"."* ]]; then 25 | echo "Cannot find matching domain record! ABORT!" 26 | exit 1 27 | fi 28 | done 29 | 30 | echo "Domain ID: ${DOMAIN_ID} | FQDN: ${FQDN} | Domain: ${DOMAIN}" 31 | 32 | mysql -ss "${CREDENTIALS}" -e "INSERT INTO ${DB}.records \ 33 | (domain_id, name, content, type,ttl,prio) VALUES \ 34 | (${DOMAIN_ID},'_acme-challenge.${FQDN}','${TOKEN}','TXT',120,NULL);" 35 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_del_challtestsrv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Simple script to update the challtestserv mock DNS server when testing DNS responses 3 | 4 | fulldomain="${1}" 5 | 6 | curl -X POST -d "{\"host\":\"_acme-challenge.${fulldomain}.\"}" http://10.30.50.3:8055/clear-txt 7 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_del_dnspod: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # need to add your email address and key to dnspod below 4 | key=${DNSPOD_API_KEY:-} 5 | 6 | fulldomain="$1" 7 | 8 | NumParts=$(echo "$fulldomain" | awk -F"." '{print NF}') 9 | if [[ $NumParts -gt 2 ]]; then 10 | domain=$(echo "$fulldomain" | awk -F\. '{print $(NF-1) FS $NF}') 11 | # txtname="_acme-challenge$(echo "$fulldomain" | awk -F\. '{for (i=1; i/dev/null | grep Auth-Sid | awk '{ print $2 }') 27 | 28 | ## put zone data in tempfile 29 | curl --silent -X POST https://dmapi.joker.com/request/dns-zone-get \ 30 | -H "Accept: application/json" -H "User-Agent: getssl/0.1" \ 31 | -H "application/x-www-form-urlencoded" -d "domain=${DOMAIN_ROOT}&auth-sid=${SID}" | \ 32 | tail -n +7 >"${TMPFILE}" 33 | 34 | ## remove txt record 35 | sed -i "/_acme-challenge.${FULLDOMAIN}.*${TOKEN}.*/d" "${TMPFILE}" 36 | 37 | ## generate encoded url data 38 | URLDATA=$(cat "${TMPFILE}" | sed 's/ /%20/g' | sed 's/"/%22/g' | sed ':a;N;$!ba;s/\n/%0A/g') 39 | 40 | ## write new zonefile to joker 41 | curl --silent --output /dev/null "https://dmapi.joker.com/request/dns-zone-put?domain=${DOMAIN_ROOT}&zone=${URLDATA}&auth-sid=${SID}" 2>&1 42 | 43 | ## remove tempfile 44 | rm -f "${TMPFILE}" 45 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_del_lexicon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # a simple wrapper for Lexicon - https://github.com/AnalogJ/lexicon - a python script which can 4 | # Manipulate DNS records on various DNS providers in a standardized way. 5 | # You need to define the following environmental variables 6 | # LEXICON_PROVIDER 7 | # Every DNS service and auth flag maps to an Environmental Variable as follows: LEXICON_{DNS Provider Name}_{Auth Type} 8 | # eg LEXICON_CLOUDFLARE_USERNAME and LEXICON_CLOUDFLARE_TOKEN or LEXICON_DIGITALOCEAN_TOKEN 9 | 10 | fulldomain="${1}" 11 | token="${2}" 12 | 13 | lexicon "$LEXICON_PROVIDER" \ 14 | delete "$fulldomain" TXT \ 15 | --name="_acme-challenge.${fulldomain}." \ 16 | --content="$token" 17 | 18 | exit 19 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_del_linode: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | fulldomain="${1}" 4 | api_url="https://api.linode.com/api/" 5 | api_key=${LINODE_KEY:-''} 6 | 7 | # Verify that required parameters are set 8 | if [[ -z "$fulldomain" ]]; then 9 | echo "DNS script requires full domain name as first parameter" 10 | exit 1 11 | fi 12 | if [[ -z "$LINODE_KEY" ]]; then 13 | echo "LINODE_KEY variable not set" 14 | exit 1 15 | fi 16 | 17 | domain_root=$(echo "$fulldomain" | awk -F\. '{print $(NF-1) FS $NF}') 18 | domain=${fulldomain%.$domain_root} 19 | txtname="_acme-challenge.$domain" 20 | 21 | # Get Domain ID 22 | response=$(curl --silent -X POST "$api_url" \ 23 | -H "Accept: application/json" -H "User-Agent: getssl/0.1" -H "application/x-www-form-urlencoded" \ 24 | -d "api_key=${api_key}&api_action=domain.list" ) 25 | domain_id=$(echo "$response" | egrep -o "{\"DOMAIN\":\"$domain_root\".*\"DOMAINID\":([0-9]+)" | egrep -o "[0-9]+$") 26 | if [[ $domain_id == "" ]]; then 27 | echo "Failed to fetch DomainID" 28 | exit 1 29 | fi 30 | 31 | # Get Resource ID 32 | response=$(curl --silent -X POST "$api_url" \ 33 | -H "Accept: application/json" -H "User-Agent: getssl/0.1" -H "application/x-www-form-urlencoded" \ 34 | -d "api_key=${api_key}&api_action=domain.resource.list&DomainID=$domain_id" ) 35 | resource_id=$(echo "$response" | egrep -o "\"RESOURCEID\":[0-9]+,\"TYPE\":\"TXT\",\"NAME\":\"$txtname\"" | egrep -o "\"RESOURCEID\":[0-9]+" | egrep -o "[0-9]+$") 36 | if [[ $resource_id == "" ]]; then 37 | echo "Failed to fetch ResourceID" 38 | exit 1 39 | fi 40 | 41 | # Delete TXT record 42 | response=$(curl --silent -X POST "$api_url" \ 43 | -H "Accept: application/json" -H "User-Agent: getssl/0.1" -H "application/x-www-form-urlencoded" \ 44 | -d "api_key=$api_key&api_action=domain.resource.delete&DomainID=$domain_id&ResourceID=$resource_id" ) 45 | errors=$(echo "$response" | egrep -o "\"ERRORARRAY\":\[.*\]") 46 | if [[ $errors != "\"ERRORARRAY\":[]" ]]; then 47 | echo "Something went wrong: $errors" 48 | exit 1 49 | fi 50 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_del_manual: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "In the DNS, the following DNS record should be deleted ;" 4 | echo "_acme-challenge.${1}" 5 | 6 | read -r -p "Press any key to obtain the certificate once the records have been updated..." 7 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_del_nsupdate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # example of script to remove token from local dns using nsupdate 4 | 5 | fulldomain="$1" 6 | token="$2" 7 | 8 | # VARIABLES: 9 | # 10 | # DNS_NSUPDATE_KEYFILE - path to a TSIG key file, if required 11 | # DNS_NSUPDATE_GETKEY - command to execute if access to the key file requires 12 | # some special action: dismounting a disk, encrypting a 13 | # file... Called with the operation 'del' and action 14 | # 'open" / 'close' 15 | 16 | if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then 17 | if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! "${DNS_NSUPDATE_KEY_HOOK}" 'del' 'open' "${fulldomain}" ; then 18 | exit $(( $? + 128 )) 19 | fi 20 | 21 | options="-k ${DNS_NSUPDATE_KEYFILE}" 22 | fi 23 | 24 | if [ -n "${DNS_SERVER}" ]; then 25 | cmd+="server ${DNS_SERVER}\n" 26 | fi 27 | 28 | cmd+="update delete ${DNS_ZONE:-"_acme-challenge.${fulldomain}."} 300 in TXT \"${token}\"\n" 29 | cmd+="\n" # blank line is a "send" command to nsupdate 30 | 31 | printf "$cmd" | nsupdate ${options} -v 32 | 33 | sts=$? 34 | 35 | if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then 36 | if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! "${DNS_NSUPDATE_KEY_HOOK}" 'del' 'close' "${fulldomain}" ; then 37 | exit $(( sts + ( $? * 10 ) )) 38 | fi 39 | fi 40 | 41 | exit ${sts} 42 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_del_ovh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | domains=($(echo "$1"|sed -e 's/^\(\([a-zA-Z0-9.-]*\?\)\.\)*\([a-zA-Z0-9-]\+\.[a-zA-Z-]\+\)$/"\1" _acme-challenge.\2 \3/g')) 4 | #challenge="$2" 5 | 6 | # Please, do not forget to ask for your credentials at https://eu.api.ovh.com/createToken/ 7 | # permissions needed are /domain/zone/* in GET,POST,DELETE 8 | applicationKey="YourAK" 9 | applicationSecret="YourAS" 10 | consumerKey="YourCK" 11 | 12 | topDomain=${domains[2]} 13 | subDomain=${domains[1]%%.} 14 | 15 | function send 16 | { 17 | method=$1 18 | url=$2 19 | body=$3 20 | ts=$(date +%s) 21 | 22 | sign=\$1\$$(echo -n "${applicationSecret}+${consumerKey}+${method}+https://eu.api.ovh.com/1.0${url}+${body}+${ts}"|sha1sum|cut -d" " -f1) 23 | curl -X "${method}" -H "Content-Type: application/json" -H "X-Ovh-Application: ${applicationKey}" -H "X-Ovh-Timestamp: ${ts}" -H "X-Ovh-Signature: ${sign}" -H "X-Ovh-Consumer: ${consumerKey}" -d "${body}" "https://eu.api.ovh.com/1.0${url}" 24 | } 25 | 26 | # Creation request 27 | oldResult=$(send GET "/domain/zone/${topDomain}/record?fieldType=TXT&subDomain=${subDomain}" ""|sed -e 's/\[//' -e 's/\]//') 28 | 29 | for num in ${oldResult//,/ } 30 | do 31 | send DELETE "/domain/zone/${topDomain}/record/${num}" "" 32 | done 33 | 34 | # Refresh request 35 | send POST "/domain/zone/${topDomain}/refresh" "" 36 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_del_pdns-mysql: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # You must either have a suitable ~/.my.cnf containing a user / pass 4 | # for your mysql / mariadb database, OR you must uncomment the next line 5 | # (which is a security risk; don't do it!) and adjust accordingly. 6 | 7 | #CREDENTIALS="-uUSERNAME -pPASSWORD" 8 | 9 | FQDN=$1 10 | 11 | # If your database name is not powerdns, change it here. 12 | DB="powerdns" 13 | 14 | mysql -ss "${CREDENTIALS}" -e "DELETE FROM ${DB}.records WHERE \ 15 | name = '_acme-challenge.${FQDN}';" 16 | 17 | echo "DELETE FROM ${DB}.records WHERE name = '_acme-challenge.${FQDN}';" 18 | -------------------------------------------------------------------------------- /contrib/md_events/dns_scripts/dns_route53.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import boto3, sys, time 4 | from os.path import basename 5 | import dns.resolver 6 | 7 | client = boto3.client('route53') 8 | 9 | name = sys.argv[0] 10 | fqdn = sys.argv[1] 11 | challenge = sys.argv[2] 12 | 13 | bname = basename(name) 14 | if bname == 'dns_add_route53': 15 | action = 'UPSERT' 16 | elif bname == 'dns_del_route53': 17 | action = 'DELETE' 18 | else: 19 | print("No such action: {a}".format(a=bname)) 20 | sys.exit(1) 21 | 22 | try: 23 | response = client.list_hosted_zones() 24 | except Exception as e: 25 | print("Oops: {e!r}".format(e=e)) 26 | sys.exit(1) 27 | 28 | zone_id = "" 29 | zone_list = dict() 30 | for zone in response['HostedZones']: 31 | if not zone['Config']['PrivateZone']: 32 | zone_list[zone['Name']] = zone['Id'] 33 | 34 | for key in sorted(zone_list.iterkeys(), key=len, reverse=True): 35 | if ".{z}".format(z=key) in ".{z}.".format(z=fqdn): 36 | zone_id = zone_list[key] 37 | 38 | if zone_id == "": 39 | print("We didn't find the zone") 40 | sys.exit(1) 41 | 42 | challenge_fqdn = "_acme-challenge.{f}".format(f=fqdn) 43 | try: 44 | response = client.change_resource_record_sets( 45 | HostedZoneId=zone_id, 46 | ChangeBatch={ 47 | 'Comment': 'getssl/Letsencrypt verification', 48 | 'Changes': [ 49 | { 50 | 'Action': action, 51 | 'ResourceRecordSet': { 52 | 'Name': challenge_fqdn, 53 | 'Type': 'TXT', 54 | 'TTL': 300, 55 | 'ResourceRecords': [{'Value': "\"{c}\"".format(c=challenge)}] 56 | } 57 | }, 58 | ] 59 | } 60 | ) 61 | except Exception as e: 62 | print("Oops: {e!r}".format(e=e)) 63 | sys.exit(1) 64 | 65 | waiting = 0 66 | if action == 'UPSERT': 67 | # Wait until we see the record before returning. The ACME server's timeout is too short. 68 | # But only if we're adding the record. Don't care how long it takes to delete. 69 | while (True): 70 | try: 71 | my_resolver = dns.resolver.Resolver(configure=False) 72 | my_resolver.nameservers = ['8.8.8.8', '8.8.4.4'] 73 | results = my_resolver.query(challenge_fqdn, 'TXT') 74 | data = str(results.response.answer[0][0]).strip('\"') 75 | if data == challenge: 76 | print("found {f} entry".format(f=challenge_fqdn)) 77 | else: 78 | print("found {f} entry but it has bad data: {d}".format(f=challenge_fqdn, 79 | d=data)) 80 | break 81 | 82 | except dns.resolver.NXDOMAIN: 83 | waiting += 10 84 | print("Didn't find {f} entry yet, sleeping... ({w}s)".format(f=challenge_fqdn, 85 | w=waiting)) 86 | time.sleep(10) 87 | pass 88 | -------------------------------------------------------------------------------- /contrib/selinux/README: -------------------------------------------------------------------------------- 1 | These are the changes to the standard SeLinux policies 2 | necessitated by mod_md as of Fedora 31 (and other distros). 3 | 4 | To install (recent Fedora): 5 | in the directory: 6 | checkmodule -m mod_md.te -o mod_md.mod 7 | semodule_package -m mod_md.mod -o mod_md.pp 8 | semodule -i mod_md.pp 9 | 10 | For older versions, replicy the "module" line with: 11 | policy_module(mod_md, 0.1.1) 12 | Link Makefile to /usr/share/selinux/devel/Makefile 13 | and run make load 14 | 15 | For other distros, check their documentation. 16 | -------------------------------------------------------------------------------- /contrib/selinux/mod_md.fc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/contrib/selinux/mod_md.fc -------------------------------------------------------------------------------- /contrib/selinux/mod_md.if: -------------------------------------------------------------------------------- 1 | ## 2 | -------------------------------------------------------------------------------- /contrib/selinux/mod_md.te: -------------------------------------------------------------------------------- 1 | module mod_md 0.1.1; 2 | 3 | require { 4 | type httpd_t; 5 | type httpd_sys_script_t; 6 | type httpd_sys_content_t; 7 | type httpd_config_t; 8 | type httpd_tmp_t; 9 | 10 | class dir {create getattr setattr read write rename open lock execute add_name remove_name unlink search rmdir}; 11 | class file {create getattr setattr read write rename open lock execute execute_no_trans unlink}; 12 | class lnk_file { read getattr }; 13 | } 14 | # mod_md (by default) maintains its data in /etc/httpd/md. /etc/httpd is labelled httpd_config_t 15 | 16 | allow httpd_t httpd_config_t:dir {create getattr setattr read write rename open lock execute add_name remove_name unlink search rmdir}; 17 | allow httpd_t httpd_config_t:file {create getattr setattr read write rename open lock execute execute_no_trans unlink}; 18 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | debian-sid: 4 | # build and run tests in a debian sid container 5 | image: ${DOCKER_REGISTRY}/mod-md-debian-sid:0.0.1 6 | container_name: mod-md-debian-sid 7 | build: 8 | context: . 9 | dockerfile: docker/debian-sid/Dockerfile 10 | labels: 11 | - "description=mod_md debian sid server" 12 | - "maintainer=stefan@eissing.org" 13 | ports: 14 | - 14000:14000 # HTTPS ACME API 15 | - 15000:15000 # HTTPS Management API 16 | 17 | volumes: 18 | - mod-md-debian-sid-data:/apache-httpd/data 19 | 20 | volumes: 21 | mod-md-debian-sid-data: 22 | name: mod-md-debian-sid-data 23 | labels: 24 | - "description=debian sid data for mod_md" 25 | - "maintainer=stefan@eissing.org" 26 | 27 | -------------------------------------------------------------------------------- /docker/debian-sid/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:sid 2 | 3 | RUN apt update; apt upgrade -y 4 | RUN apt install -y apt-listchanges \ 5 | make openssl libssl-dev libcurl4 libcurl4-openssl-dev \ 6 | gcc subversion git cargo python3 iputils-ping \ 7 | libapr1-dev libaprutil1-dev libnghttp2-dev pip \ 8 | autoconf libtool libtool-bin libpcre3-dev libjansson-dev curl rsync nghttp2-client 9 | 10 | RUN pip install pytest tqdm pycurl cryptography requests pyopenssl 11 | 12 | RUN apt install -y apache2 apache2-dev libapache2-mod-md 13 | RUN apt install -y pebble 14 | 15 | COPY docker/debian-sid/bin/* /apache-httpd/bin/ 16 | COPY configure.ac Makefile.am NEWS README* AUTHORS ChangeLog COPYING LICENSE /apache-httpd/mod_md/ 17 | COPY m4 /apache-httpd/mod_md/m4 18 | COPY src /apache-httpd/mod_md/src 19 | COPY test test/Makefile.am /apache-httpd/mod_md/test/ 20 | COPY test/pyhttpd /apache-httpd/mod_md/test/pyhttpd 21 | COPY test/modules /apache-httpd/mod_md/test/modules 22 | COPY test/unit /apache-httpd/mod_md/test/unit 23 | 24 | CMD ["/bin/bash", "-c", "/apache-httpd/bin/mod_md_test.sh"] -------------------------------------------------------------------------------- /docker/debian-sid/bin/mod_md_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TOP=/apache-httpd 4 | DATADIR=$TOP/data 5 | 6 | fail() { 7 | echo "$@" 8 | exit 1 9 | } 10 | 11 | needs_update() { 12 | local ref_file="$1" 13 | local check_dir="$2" 14 | if test ! -f "$ref_file"; then 15 | return 0 16 | fi 17 | find "$check_dir" -type f -a -newer "$ref_file" -o -type d -name .git -prune -a -false | 18 | while read fname; do 19 | return 0 20 | done 21 | return 1 22 | } 23 | 24 | PREFIX=$(apxs -q exec_prefix) 25 | if test ! -d $PREFIX; then 26 | fail "apache install prefix not found: $PREFIX" 27 | fi 28 | 29 | # remove some stuff that accumulates 30 | LOG_DIR=$(apxs -q logfiledir) 31 | rm -f $LOG_DIR/* 32 | 33 | cd "$TOP/mod_md" ||fail 34 | if needs_update .installed .; then 35 | rm -f .installed 36 | if test ! -f configure -o configure.ac -nt configure; then 37 | autoreconf -i ||fail 38 | fi 39 | if test ! -d Makefile -o ./configure -nt Makefile; then 40 | ./configure || fail 41 | touch ./configure 42 | fi 43 | make clean||fail 44 | make ||fail 45 | find . 46 | touch .installed 47 | fi 48 | make install ||fail 49 | #pytest -vvv -k test_300_021 || cat test/gen/apache/logs/error_log 50 | pytest 51 | -------------------------------------------------------------------------------- /m4/ax_check_compile_flag.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Check whether the given FLAG works with the current language's compiler 12 | # or gives an error. (Warnings, however, are ignored) 13 | # 14 | # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on 15 | # success/failure. 16 | # 17 | # If EXTRA-FLAGS is defined, it is added to the current language's default 18 | # flags (e.g. CFLAGS) when the check is done. The check is thus made with 19 | # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to 20 | # force the compiler to issue an error when a bad flag is given. 21 | # 22 | # INPUT gives an alternative input source to AC_COMPILE_IFELSE. 23 | # 24 | # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this 25 | # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. 26 | # 27 | # LICENSE 28 | # 29 | # Copyright (c) 2008 Guido U. Draheim 30 | # Copyright (c) 2011 Maarten Bosmans 31 | # 32 | # This program is free software: you can redistribute it and/or modify it 33 | # under the terms of the GNU General Public License as published by the 34 | # Free Software Foundation, either version 3 of the License, or (at your 35 | # option) any later version. 36 | # 37 | # This program is distributed in the hope that it will be useful, but 38 | # WITHOUT ANY WARRANTY; without even the implied warranty of 39 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 40 | # Public License for more details. 41 | # 42 | # You should have received a copy of the GNU General Public License along 43 | # with this program. If not, see . 44 | # 45 | # As a special exception, the respective Autoconf Macro's copyright owner 46 | # gives unlimited permission to copy, distribute and modify the configure 47 | # scripts that are the output of Autoconf when processing the Macro. You 48 | # need not follow the terms of the GNU General Public License when using 49 | # or distributing such scripts, even though portions of the text of the 50 | # Macro appear in them. The GNU General Public License (GPL) does govern 51 | # all other use of the material that constitutes the Autoconf Macro. 52 | # 53 | # This special exception to the GPL applies to versions of the Autoconf 54 | # Macro released by the Autoconf Archive. When you make and distribute a 55 | # modified version of the Autoconf Macro, you may extend this special 56 | # exception to the GPL to apply to your modified version as well. 57 | 58 | #serial 3 59 | 60 | AC_DEFUN([AX_CHECK_COMPILE_FLAG], 61 | [AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX 62 | AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl 63 | AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ 64 | ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS 65 | _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" 66 | AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], 67 | [AS_VAR_SET(CACHEVAR,[yes])], 68 | [AS_VAR_SET(CACHEVAR,[no])]) 69 | _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) 70 | AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes], 71 | [m4_default([$2], :)], 72 | [m4_default([$3], :)]) 73 | AS_VAR_POPDEF([CACHEVAR])dnl 74 | ])dnl AX_CHECK_COMPILE_FLAGS 75 | -------------------------------------------------------------------------------- /mod_md_lib/main.c: -------------------------------------------------------------------------------- 1 | // 2 | // main.c 3 | // mod_md_lib 4 | // 5 | // Created by Stefan Eissing on 29.05.19. 6 | // 7 | 8 | #include 9 | 10 | int main(int argc, const char * argv[]) { 11 | // insert code here... 12 | printf("Hello, World!\n"); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /mod_md_ocsp_status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/mod_md_ocsp_status.png -------------------------------------------------------------------------------- /mod_md_status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/mod_md_status.png -------------------------------------------------------------------------------- /patches/mod_ssl_md2-2.4.x.diff: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /patches/mod_ssl_md2-trunk.diff: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/contributors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #*************************************************************************** 3 | # _ _ ____ _ 4 | # Project ___| | | | _ \| | 5 | # / __| | | | |_) | | 6 | # | (__| |_| | _ <| |___ 7 | # \___|\___/|_| \_\_____| 8 | # 9 | # Copyright (C) 2013-2018, Daniel Stenberg, , et al. 10 | # 11 | # This software is licensed as described in the file COPYING, which 12 | # you should have received as part of this distribution. The terms 13 | # are also available at https://curl.haxx.se/docs/copyright.html. 14 | # 15 | # You may opt to use, copy, modify, merge, publish, distribute and/or sell 16 | # copies of the Software, and permit persons to whom the Software is 17 | # furnished to do so, under the terms of the COPYING file. 18 | # 19 | # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 20 | # KIND, either express or implied. 21 | # 22 | ########################################################################### 23 | # 24 | # This script was inspired, partly copied and modified from the great curl project. 25 | # see 26 | # 27 | # 28 | 29 | rcommit="" 30 | 31 | if test $# -le 0; then 32 | rtag=$( git tag --sort=authordate | tail -n 1 ) 33 | else 34 | case "$1" in 35 | v*) 36 | rtag="$1" 37 | ;; 38 | *) 39 | rcommit=$1 40 | ;; 41 | esac 42 | fi 43 | 44 | if test -n "$rtag"; then 45 | git show ${rtag} -w >/dev/null || exit 1 46 | rcommit=$( git show ${rtag} -w |egrep '^commit '|cut -d' ' -f2 ) 47 | echo "since-release: ${rtag}" 48 | fi 49 | 50 | echo "since-commit: $rcommit" 51 | ( 52 | git log --use-mailmap $rcommit..HEAD | \ 53 | egrep -ai '(^Author|^Commit|by):' | \ 54 | cut -d: -f2- | \ 55 | cut '-d(' -f1 | \ 56 | cut '-d<' -f1 | \ 57 | tr , '\012' | \ 58 | sed 's/ at github/ on github/' | \ 59 | sed 's/ and /\n/' | \ 60 | sed -e 's/^ //' -e 's/ $//g' -e 's/@users.noreply.github.com$/ on github/' 61 | 62 | )| \ 63 | grep -a ' ' | \ 64 | sort -fu | \ 65 | awk ' 66 | BEGIN { 67 | sep = "" 68 | field="contributor-names: " 69 | } 70 | 71 | { 72 | num++; 73 | n = sprintf("%s%s%s", n, sep, $0); 74 | sep = ", " 75 | if(length(n) > 77) { 76 | printf("%s%s%s\n", field, p, sep); 77 | field=" " 78 | n=sprintf("%s", $0); 79 | } 80 | p=n; 81 | } 82 | 83 | END { 84 | printf("%s%s\n", field, p); 85 | printf("contributor-count: %d\n", num); 86 | } 87 | ' 88 | -------------------------------------------------------------------------------- /scripts/md_message.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Sample script to configure for MDMessageCmd. 4 | # 5 | # Edit the USER you want to notify. Comment the "msg=" lines where 6 | # you do not want to receive notifications for. 7 | # 8 | 9 | action="$1" 10 | domain="$2" 11 | 12 | USER="webmaster@$domain" 13 | 14 | case "$action" in 15 | "renewing") 16 | subject="renewing $domain certificate" 17 | msg="Your Apache starts renewing the certificate for '$domain'." 18 | ;; 19 | "renewed") 20 | subject="renewed $domain certificate" 21 | msg="Your Apache renewed the certificate for '$domain'. It will become active after a server reload." 22 | ;; 23 | "installed") 24 | subject="installed $domain certificate" 25 | msg="Your Apache installed the certificate for '$domain'. It is now active." 26 | ;; 27 | "expiring") 28 | subject="expiring $domain certificate" 29 | msg="Your Apache reports that the certificate for '$domain' will soon expire." 30 | ;; 31 | "errored") 32 | subject="error renewing $domain certificate" 33 | msg="There was an error renewing the certificate for '$domain'. Apache will continue trying. Please check the md-status resources or the server log for more information should this repeat." 34 | ;; 35 | "ocsp-renewed") 36 | subject="refreshed OCSP stapling for $domain" 37 | msg="The OCSP stapling information for '$domain' was successfully refreshed." 38 | ;; 39 | "ocsp-errored") 40 | subject="error refreshing OCSP stapling for $domain" 41 | msg="The was an error refreshing the OCSP stapling information for '$domain'. Apache will continue trying. Please check the md-status resources or the server log for more information should this repeat." 42 | ;; 43 | *) 44 | subject="unknown action in MD message" 45 | msg="Your Apache reported action '$action' for domain '$domain'." 46 | esac 47 | 48 | if test "x$msg" = "x"; then exit 0; fi 49 | 50 | mail -s "$subject" "$USER" < 18 | #include 19 | #include 20 | 21 | #include "md.h" 22 | #include "md_event.h" 23 | 24 | 25 | typedef struct md_subscription { 26 | struct md_subscription *next; 27 | md_event_cb *cb; 28 | void *baton; 29 | } md_subscription; 30 | 31 | static struct { 32 | apr_pool_t *p; 33 | md_subscription *subs; 34 | } EVNT; 35 | 36 | static apr_status_t cleanup_setup(void *dummy) 37 | { 38 | (void)dummy; 39 | memset(&EVNT, 0, sizeof(EVNT)); 40 | return APR_SUCCESS; 41 | } 42 | 43 | void md_event_init(apr_pool_t *p) 44 | { 45 | memset(&EVNT, 0, sizeof(EVNT)); 46 | EVNT.p = p; 47 | apr_pool_cleanup_register(p, NULL, cleanup_setup, apr_pool_cleanup_null); 48 | } 49 | 50 | void md_event_subscribe(md_event_cb *cb, void *baton) 51 | { 52 | md_subscription *sub; 53 | 54 | sub = apr_pcalloc(EVNT.p, sizeof(*sub)); 55 | sub->cb = cb; 56 | sub->baton = baton; 57 | sub->next = EVNT.subs; 58 | EVNT.subs = sub; 59 | } 60 | 61 | apr_status_t md_event_raise(const char *event, 62 | const char *mdomain, 63 | struct md_job_t *job, 64 | struct md_result_t *result, 65 | apr_pool_t *p) 66 | { 67 | md_subscription *sub = EVNT.subs; 68 | apr_status_t rv; 69 | 70 | while (sub) { 71 | rv = sub->cb(event, mdomain, sub->baton, job, result, p); 72 | if (APR_SUCCESS != rv) return rv; 73 | sub = sub->next; 74 | } 75 | return APR_SUCCESS; 76 | } 77 | 78 | void md_event_holler(const char *event, 79 | const char *mdomain, 80 | struct md_job_t *job, 81 | struct md_result_t *result, 82 | apr_pool_t *p) 83 | { 84 | md_subscription *sub = EVNT.subs; 85 | while (sub) { 86 | sub->cb(event, mdomain, sub->baton, job, result, p); 87 | sub = sub->next; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/md_event.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef md_event_h 18 | #define md_event_h 19 | 20 | struct md_job_t; 21 | struct md_result_t; 22 | 23 | typedef apr_status_t md_event_cb(const char *event, 24 | const char *mdomain, 25 | void *baton, 26 | struct md_job_t *job, 27 | struct md_result_t *result, 28 | apr_pool_t *p); 29 | 30 | void md_event_init(apr_pool_t *p); 31 | 32 | void md_event_subscribe(md_event_cb *cb, void *baton); 33 | 34 | apr_status_t md_event_raise(const char *event, 35 | const char *mdomain, 36 | struct md_job_t *job, 37 | struct md_result_t *result, 38 | apr_pool_t *p); 39 | 40 | void md_event_holler(const char *event, 41 | const char *mdomain, 42 | struct md_job_t *job, 43 | struct md_result_t *result, 44 | apr_pool_t *p); 45 | 46 | #endif /* md_event_h */ 47 | -------------------------------------------------------------------------------- /src/md_jws.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_jws_h 18 | #define mod_md_md_jws_h 19 | 20 | struct apr_table_t; 21 | struct md_json_t; 22 | struct md_pkey_t; 23 | struct md_data_t; 24 | 25 | /** 26 | * Get the JSON value of the 'jwk' field for the given key. 27 | */ 28 | apr_status_t md_jws_get_jwk(md_json_t **pjwk, apr_pool_t *p, struct md_pkey_t *pkey); 29 | 30 | /** 31 | * Get the JWS key signed JSON message with given payload and protected fields, signed 32 | * using the given key and optional key_id. 33 | */ 34 | apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p, 35 | struct md_data_t *payload, md_json_t *prot_fields, 36 | struct md_pkey_t *pkey, const char *key_id); 37 | /** 38 | * Get the 'Thumbprint' as defined in RFC8555 for the given key in 39 | * base64 encoding. 40 | */ 41 | apr_status_t md_jws_pkey_thumb(const char **pthumb64, apr_pool_t *p, struct md_pkey_t *pkey); 42 | 43 | /** 44 | * Get the JWS HS256 signed message for given payload and protected fields, 45 | * using the base64 encoded MAC key. 46 | */ 47 | apr_status_t md_jws_hmac(md_json_t **pmsg, apr_pool_t *p, 48 | struct md_data_t *payload, md_json_t *prot_fields, 49 | const struct md_data_t *hmac_key); 50 | 51 | 52 | #endif /* md_jws_h */ 53 | -------------------------------------------------------------------------------- /src/md_log.c: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "md_log.h" 22 | 23 | #define LOG_BUFFER_LEN 1024 24 | 25 | static const char *level_names[] = { 26 | "emergency", 27 | "alert", 28 | "crit", 29 | "err", 30 | "warning", 31 | "notice", 32 | "info", 33 | "debug", 34 | "trace1", 35 | "trace2", 36 | "trace3", 37 | "trace4", 38 | "trace5", 39 | "trace6", 40 | "trace7", 41 | "trace8", 42 | }; 43 | 44 | const char *md_log_level_name(md_log_level_t level) 45 | { 46 | return level_names[level]; 47 | } 48 | 49 | static md_log_print_cb *log_printv; 50 | static md_log_level_cb *log_level; 51 | static void *log_baton; 52 | 53 | void md_log_set(md_log_level_cb *level_cb, md_log_print_cb *print_cb, void *baton) 54 | { 55 | log_printv = print_cb; 56 | log_level = level_cb; 57 | log_baton = baton; 58 | } 59 | 60 | int md_log_is_level(apr_pool_t *p, md_log_level_t level) 61 | { 62 | if (!log_level) { 63 | return 0; 64 | } 65 | return log_level(log_baton, p, level); 66 | } 67 | 68 | void md_log_perror(const char *file, int line, md_log_level_t level, 69 | apr_status_t rv, apr_pool_t *p, const char *fmt, ...) 70 | { 71 | va_list ap; 72 | 73 | va_start(ap, fmt); 74 | if (log_printv) { 75 | log_printv(file, line, level, rv, log_baton, p, fmt, ap); 76 | } 77 | va_end(ap); 78 | } 79 | -------------------------------------------------------------------------------- /src/md_log.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_log_h 18 | #define mod_md_md_log_h 19 | 20 | typedef enum { 21 | MD_LOG_EMERG, 22 | MD_LOG_ALERT, 23 | MD_LOG_CRIT, 24 | MD_LOG_ERR, 25 | MD_LOG_WARNING, 26 | MD_LOG_NOTICE, 27 | MD_LOG_INFO, 28 | MD_LOG_DEBUG, 29 | MD_LOG_TRACE1, 30 | MD_LOG_TRACE2, 31 | MD_LOG_TRACE3, 32 | MD_LOG_TRACE4, 33 | MD_LOG_TRACE5, 34 | MD_LOG_TRACE6, 35 | MD_LOG_TRACE7, 36 | MD_LOG_TRACE8, 37 | } md_log_level_t; 38 | 39 | #define MD_LOG_MARK __FILE__,__LINE__ 40 | 41 | #ifndef APLOGNO 42 | #define APLOGNO(n) "AH" #n ": " 43 | #endif 44 | 45 | const char *md_log_level_name(md_log_level_t level); 46 | 47 | int md_log_is_level(apr_pool_t *p, md_log_level_t level); 48 | 49 | void md_log_perror(const char *file, int line, md_log_level_t level, 50 | apr_status_t rv, apr_pool_t *p, const char *fmt, ...) 51 | __attribute__((format(printf,6,7))); 52 | 53 | typedef int md_log_level_cb(void *baton, apr_pool_t *p, md_log_level_t level); 54 | 55 | typedef void md_log_print_cb(const char *file, int line, md_log_level_t level, 56 | apr_status_t rv, void *baton, apr_pool_t *p, const char *fmt, va_list ap); 57 | 58 | void md_log_set(md_log_level_cb *level_cb, md_log_print_cb *print_cb, void *baton); 59 | 60 | #endif /* md_log_h */ 61 | -------------------------------------------------------------------------------- /src/md_ocsp.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef md_ocsp_h 18 | #define md_ocsp_h 19 | 20 | struct md_data_t; 21 | struct md_job_t; 22 | struct md_json_t; 23 | struct md_result_t; 24 | struct md_store_t; 25 | struct md_timeslice_t; 26 | 27 | typedef enum { 28 | MD_OCSP_CERT_ST_UNKNOWN, 29 | MD_OCSP_CERT_ST_GOOD, 30 | MD_OCSP_CERT_ST_REVOKED, 31 | } md_ocsp_cert_stat_t; 32 | 33 | const char *md_ocsp_cert_stat_name(md_ocsp_cert_stat_t stat); 34 | md_ocsp_cert_stat_t md_ocsp_cert_stat_value(const char *name); 35 | 36 | typedef struct md_ocsp_reg_t md_ocsp_reg_t; 37 | 38 | apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p, 39 | struct md_store_t *store, 40 | const md_timeslice_t *renew_window, 41 | const char *user_agent, const char *proxy_url, 42 | apr_time_t min_delay); 43 | 44 | apr_status_t md_ocsp_init_id(struct md_data_t *id, apr_pool_t *p, const md_cert_t *cert); 45 | 46 | apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, const char *ext_id, apr_size_t ext_id_len, 47 | md_cert_t *x, md_cert_t *issuer, const md_t *md); 48 | 49 | typedef void md_ocsp_copy_der(const unsigned char *der, apr_size_t der_len, void *userdata); 50 | 51 | apr_status_t md_ocsp_get_status(md_ocsp_copy_der *cb, void *userdata, md_ocsp_reg_t *reg, 52 | const char *ext_id, apr_size_t ext_id_len, 53 | apr_pool_t *p, const md_t *md); 54 | 55 | apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid, 56 | md_ocsp_reg_t *reg, const md_cert_t *cert, 57 | apr_pool_t *p, const md_t *md); 58 | 59 | apr_size_t md_ocsp_count(md_ocsp_reg_t *reg); 60 | 61 | void md_ocsp_renew(md_ocsp_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, apr_time_t *pnext_run); 62 | 63 | apr_status_t md_ocsp_remove_responses_older_than(md_ocsp_reg_t *reg, apr_pool_t *p, 64 | apr_time_t timestamp); 65 | 66 | void md_ocsp_get_summary(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p); 67 | void md_ocsp_get_status_all(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p); 68 | 69 | struct md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p); 70 | 71 | #endif /* md_ocsp_h */ 72 | -------------------------------------------------------------------------------- /src/md_store_fs.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_store_fs_h 18 | #define mod_md_md_store_fs_h 19 | 20 | struct md_store_t; 21 | 22 | /** 23 | * Default file permissions set by the store, user only read/write(/exec), 24 | * if so supported by the apr. 25 | */ 26 | #define MD_FPROT_F_UONLY (APR_FPROT_UREAD|APR_FPROT_UWRITE) 27 | #define MD_FPROT_D_UONLY (MD_FPROT_F_UONLY|APR_FPROT_UEXECUTE) 28 | 29 | /** 30 | * User has all permission, group can read, other none 31 | */ 32 | #define MD_FPROT_F_UALL_GREAD (MD_FPROT_F_UONLY|APR_FPROT_GREAD) 33 | #define MD_FPROT_D_UALL_GREAD (MD_FPROT_D_UONLY|APR_FPROT_GREAD|APR_FPROT_GEXECUTE) 34 | 35 | /** 36 | * User has all permission, group and others can read 37 | */ 38 | #define MD_FPROT_F_UALL_WREAD (MD_FPROT_F_UALL_GREAD|APR_FPROT_WREAD) 39 | #define MD_FPROT_D_UALL_WREAD (MD_FPROT_D_UALL_GREAD|APR_FPROT_WREAD|APR_FPROT_WEXECUTE) 40 | 41 | apr_status_t md_store_fs_init(struct md_store_t **pstore, apr_pool_t *p, 42 | const char *path); 43 | 44 | 45 | apr_status_t md_store_fs_default_perms_set(struct md_store_t *store, 46 | apr_fileperms_t file_perms, 47 | apr_fileperms_t dir_perms); 48 | apr_status_t md_store_fs_group_perms_set(struct md_store_t *store, 49 | md_store_group_t group, 50 | apr_fileperms_t file_perms, 51 | apr_fileperms_t dir_perms); 52 | 53 | typedef enum { 54 | MD_S_FS_EV_CREATED, 55 | MD_S_FS_EV_MOVED, 56 | } md_store_fs_ev_t; 57 | 58 | typedef apr_status_t md_store_fs_cb(void *baton, struct md_store_t *store, 59 | md_store_fs_ev_t ev, unsigned int group, 60 | const char *fname, apr_filetype_e ftype, 61 | apr_pool_t *p); 62 | 63 | apr_status_t md_store_fs_set_event_cb(struct md_store_t *store, md_store_fs_cb *cb, void *baton); 64 | 65 | #endif /* mod_md_md_store_fs_h */ 66 | -------------------------------------------------------------------------------- /src/md_tailscale.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_tailscale_h 18 | #define mod_md_md_tailscale_h 19 | 20 | #define MD_PROTO_TAILSCALE "tailscale" 21 | 22 | apr_status_t md_tailscale_protos_add(struct apr_hash_t *protos, apr_pool_t *p); 23 | 24 | #endif /* mod_md_md_tailscale_h */ 25 | 26 | -------------------------------------------------------------------------------- /src/md_time.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_time_h 18 | #define mod_md_md_time_h 19 | 20 | #include 21 | 22 | #define MD_SECS_PER_HOUR (60*60) 23 | #define MD_SECS_PER_DAY (24*MD_SECS_PER_HOUR) 24 | 25 | typedef struct md_timeperiod_t md_timeperiod_t; 26 | 27 | struct md_timeperiod_t { 28 | apr_time_t start; 29 | apr_time_t end; 30 | }; 31 | 32 | apr_time_t md_timeperiod_length(const md_timeperiod_t *period); 33 | 34 | int md_timeperiod_contains(const md_timeperiod_t *period, apr_time_t time); 35 | int md_timeperiod_has_started(const md_timeperiod_t *period, apr_time_t time); 36 | int md_timeperiod_has_ended(const md_timeperiod_t *period, apr_time_t time); 37 | apr_interval_time_t md_timeperiod_remaining(const md_timeperiod_t *period, apr_time_t time); 38 | 39 | /** 40 | * Return the timeperiod common between a and b. If both do not overlap, return {0,0}. 41 | */ 42 | md_timeperiod_t md_timeperiod_common(const md_timeperiod_t *a, const md_timeperiod_t *b); 43 | 44 | char *md_timeperiod_print(apr_pool_t *p, const md_timeperiod_t *period); 45 | 46 | /** 47 | * Print a human readable form of the give duration in days/hours/min/sec 48 | */ 49 | const char *md_duration_print(apr_pool_t *p, apr_interval_time_t duration); 50 | const char *md_duration_roughly(apr_pool_t *p, apr_interval_time_t duration); 51 | 52 | /** 53 | * Parse a machine readable string duration in the form of NN[unit], where 54 | * unit is d/h/mi/s/ms with the default given should the unit not be specified. 55 | */ 56 | apr_status_t md_duration_parse(apr_interval_time_t *ptimeout, const char *value, 57 | const char *def_unit); 58 | const char *md_duration_format(apr_pool_t *p, apr_interval_time_t duration); 59 | 60 | typedef struct { 61 | apr_interval_time_t norm; /* if > 0, normalized base length */ 62 | apr_interval_time_t len; /* length of the timespan */ 63 | } md_timeslice_t; 64 | 65 | apr_status_t md_timeslice_create(md_timeslice_t **pts, apr_pool_t *p, 66 | apr_interval_time_t norm, apr_interval_time_t len); 67 | 68 | int md_timeslice_eq(const md_timeslice_t *ts1, const md_timeslice_t *ts2); 69 | 70 | const char *md_timeslice_parse(md_timeslice_t **pts, apr_pool_t *p, 71 | const char *val, apr_interval_time_t defnorm); 72 | const char *md_timeslice_format(const md_timeslice_t *ts, apr_pool_t *p); 73 | 74 | md_timeperiod_t md_timeperiod_slice_before_end(const md_timeperiod_t *period, 75 | const md_timeslice_t *ts); 76 | 77 | #endif /* md_util_h */ 78 | -------------------------------------------------------------------------------- /src/md_version.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_version_h 18 | #define mod_md_md_version_h 19 | 20 | #undef PACKAGE_VERSION 21 | #undef PACKAGE_TARNAME 22 | #undef PACKAGE_STRING 23 | #undef PACKAGE_NAME 24 | #undef PACKAGE_BUGREPORT 25 | 26 | /** 27 | * @macro 28 | * Version number of the md module as c string 29 | */ 30 | #define MOD_MD_VERSION "2.5.2-git" 31 | 32 | /** 33 | * @macro 34 | * Numerical representation of the version number of the md module 35 | * release. This is a 24 bit number with 8 bits for major number, 8 bits 36 | * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. 37 | */ 38 | #define MOD_MD_VERSION_NUM 0x020502 39 | 40 | #define MD_ACME_DEF_URL "https://acme-v02.api.letsencrypt.org/directory" 41 | #define MD_TAILSCALE_DEF_URL "file://localhost/var/run/tailscale/tailscaled.sock" 42 | 43 | #endif /* mod_md_md_version_h */ 44 | -------------------------------------------------------------------------------- /src/md_version.h.in: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_version_h 18 | #define mod_md_md_version_h 19 | 20 | #undef PACKAGE_VERSION 21 | #undef PACKAGE_TARNAME 22 | #undef PACKAGE_STRING 23 | #undef PACKAGE_NAME 24 | #undef PACKAGE_BUGREPORT 25 | 26 | /** 27 | * @macro 28 | * Version number of the md module as c string 29 | */ 30 | #define MOD_MD_VERSION "@PACKAGE_VERSION@-git" 31 | 32 | /** 33 | * @macro 34 | * Numerical representation of the version number of the md module 35 | * release. This is a 24 bit number with 8 bits for major number, 8 bits 36 | * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. 37 | */ 38 | #define MOD_MD_VERSION_NUM @PACKAGE_VERSION_NUM@ 39 | 40 | #define MD_ACME_DEF_URL "@ACME_DEF_URL@" 41 | #define MD_TAILSCALE_DEF_URL "@TAILSCALE_DEF_URL@" 42 | 43 | #endif /* mod_md_md_version_h */ 44 | -------------------------------------------------------------------------------- /src/mod_md.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_mod_md_h 18 | #define mod_md_mod_md_h 19 | 20 | #endif /* mod_md_mod_md_h */ 21 | -------------------------------------------------------------------------------- /src/mod_md_drive.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_drive_h 18 | #define mod_md_md_drive_h 19 | 20 | struct md_mod_conf_t; 21 | struct md_reg_t; 22 | 23 | typedef struct md_renew_ctx_t md_renew_ctx_t; 24 | 25 | int md_will_renew_cert(const md_t *md); 26 | 27 | /** 28 | * Start driving the certificate renewal for MDs marked with watched. 29 | */ 30 | apr_status_t md_renew_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr_pool_t *p); 31 | 32 | 33 | 34 | 35 | #endif /* mod_md_md_drive_h */ 36 | -------------------------------------------------------------------------------- /src/mod_md_ocsp.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_ocsp_h 18 | #define mod_md_md_ocsp_h 19 | 20 | 21 | int md_ocsp_prime_status(server_rec *s, apr_pool_t *p, 22 | const char *id, apr_size_t id_len, const char *pem); 23 | 24 | int md_ocsp_provide_status(server_rec *s, conn_rec *c, const char *id, apr_size_t id_len, 25 | ap_ssl_ocsp_copy_resp *cb, void *userdata); 26 | 27 | /** 28 | * Start watchdog for retrieving/updating ocsp status. 29 | */ 30 | apr_status_t md_ocsp_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr_pool_t *p); 31 | 32 | 33 | #endif /* mod_md_md_ocsp_h */ 34 | -------------------------------------------------------------------------------- /src/mod_md_os.c: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #if APR_HAVE_UNISTD_H 26 | #include 27 | #endif 28 | #if AP_NEED_SET_MUTEX_PERMS 29 | #include "unixd.h" 30 | #endif 31 | 32 | #include "md_util.h" 33 | #include "mod_md_os.h" 34 | 35 | apr_status_t md_try_chown(const char *fname, unsigned int uid, int gid, apr_pool_t *p) 36 | { 37 | #if AP_NEED_SET_MUTEX_PERMS && HAVE_UNISTD_H 38 | /* Since we only switch user when running as root, we only need to chown directories 39 | * in that case. Otherwise, the server will ignore any "user/group" directives and 40 | * child processes have the same privileges as the parent. 41 | */ 42 | if (!geteuid()) { 43 | if (-1 == chown(fname, (uid_t)uid, (gid_t)gid)) { 44 | apr_status_t rv = APR_FROM_OS_ERROR(errno); 45 | if (!APR_STATUS_IS_ENOENT(rv)) { 46 | ap_log_perror(APLOG_MARK, APLOG_ERR, rv, p, APLOGNO(10082) 47 | "Can't change owner of %s", fname); 48 | } 49 | return rv; 50 | } 51 | } 52 | return APR_SUCCESS; 53 | #else 54 | return APR_ENOTIMPL; 55 | #endif 56 | } 57 | 58 | apr_status_t md_make_worker_accessible(const char *fname, apr_pool_t *p) 59 | { 60 | #ifdef WIN32 61 | return APR_ENOTIMPL; 62 | #else 63 | return md_try_chown(fname, ap_unixd_config.user_id, -1, p); 64 | #endif 65 | } 66 | 67 | #ifdef WIN32 68 | 69 | apr_status_t md_server_graceful(apr_pool_t *p, server_rec *s) 70 | { 71 | return APR_ENOTIMPL; 72 | } 73 | 74 | #else 75 | 76 | apr_status_t md_server_graceful(apr_pool_t *p, server_rec *s) 77 | { 78 | apr_status_t rv; 79 | 80 | (void)p; 81 | (void)s; 82 | rv = (kill(getppid(), AP_SIG_GRACEFUL) < 0)? APR_ENOTIMPL : APR_SUCCESS; 83 | ap_log_error(APLOG_MARK, APLOG_TRACE1, errno, NULL, "sent signal to parent"); 84 | return rv; 85 | } 86 | 87 | #endif 88 | 89 | -------------------------------------------------------------------------------- /src/mod_md_os.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_os_h 18 | #define mod_md_md_os_h 19 | 20 | /** 21 | * Try chown'ing the file/directory. Give id -1 to not change uid/gid. 22 | * Will return APR_ENOTIMPL on platforms not supporting this operation. 23 | */ 24 | apr_status_t md_try_chown(const char *fname, unsigned int uid, int gid, apr_pool_t *p); 25 | 26 | /** 27 | * Make a file or directory read/write(/searchable) by httpd workers. 28 | */ 29 | apr_status_t md_make_worker_accessible(const char *fname, apr_pool_t *p); 30 | 31 | /** 32 | * Trigger a graceful restart of the server. Depending on the architecture, may 33 | * return APR_ENOTIMPL. 34 | */ 35 | apr_status_t md_server_graceful(apr_pool_t *p, server_rec *s); 36 | 37 | #endif /* mod_md_md_os_h */ 38 | -------------------------------------------------------------------------------- /src/mod_md_private.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_private_h 18 | #define mod_md_md_private_h 19 | 20 | extern module AP_MODULE_DECLARE_DATA md_module; 21 | 22 | APLOG_USE_MODULE(md); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/mod_md_status.h: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef mod_md_md_status_h 18 | #define mod_md_md_status_h 19 | 20 | int md_http_cert_status(request_rec *r); 21 | 22 | int md_domains_status_hook(request_rec *r, int flags); 23 | int md_ocsp_status_hook(request_rec *r, int flags); 24 | 25 | int md_status_handler(request_rec *r); 26 | 27 | #endif /* mod_md_md_status_h */ 28 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | Makefile.in 2 | Makefile 3 | __pycache__ 4 | .test_dir 5 | gen 6 | conf/global.conf 7 | conf/httpd_http.conf 8 | conf/httpd_https.conf 9 | conf/modules.conf 10 | data/test_conf_validate/test_014.conf 11 | *.pyc 12 | data/test_roundtrip/temp.conf 13 | config.ini -------------------------------------------------------------------------------- /test/Makefile.am: -------------------------------------------------------------------------------- 1 | # Copyright 2019 greenbytes GmbH (https://www.greenbytes.de) 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | SERVER_DIR = @SERVER_DIR@ 17 | GEN = gen 18 | ACME_TEST_URL = @ACME_TEST_URL@ 19 | ACME_TEST_DIR = @ACME_TEST_DIR@ 20 | 21 | 22 | .phony: unit_tests 23 | 24 | EXTRA_DIST = modules pyhttpd unit 25 | 26 | dist-hook: 27 | rm -rf $(distdir)/pyhttpd/htdocs/test1/apache.org-files 28 | rm -rf $(distdir)/pyhttpd/__pycache__ 29 | rm -rf $(distdir)/modules/*/__pycache__ 30 | 31 | 32 | if BUILD_UNIT_TESTS 33 | TESTS = unit/main 34 | 35 | check_PROGRAMS = unit/main 36 | 37 | unit_main_SOURCES = unit/main.c unit/test_md_json.c unit/test_md_util.c unit/test_common.h 38 | unit_main_LDADD = $(top_builddir)/src/libmd.la 39 | 40 | unit_main_CFLAGS = $(CHECK_CFLAGS) -I$(top_srcdir)/src 41 | unit_main_LDADD += $(CHECK_LIBS) -l$(LIB_APR) -l$(LIB_APRUTIL) 42 | 43 | unit_tests: $(TESTS) 44 | @echo "============================= unit tests (check) ===============================" 45 | @$(TESTS) 46 | else 47 | 48 | unit_tests: $(TESTS) 49 | @echo "unit tests disabled" 50 | 51 | endif 52 | 53 | test: unit_tests 54 | pytest 55 | 56 | clean-local: 57 | rm -f $(SERVER_DIR)/conf/ssl/* 58 | rm -rf *.pyc __pycache__ 59 | rm -f data/ssl/valid* 60 | rm -rf $(SERVER_DIR) 61 | -------------------------------------------------------------------------------- /test/modules/md/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/modules/md/__init__.py -------------------------------------------------------------------------------- /test/modules/md/conftest.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | import pytest 5 | 6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) 7 | 8 | from .md_env import MDTestEnv 9 | from .md_acme import MDPebbleRunner, MDBoulderRunner 10 | 11 | 12 | def pytest_report_header(config): 13 | env = MDTestEnv() 14 | return "mod_md: [apache: {aversion}({prefix}), mod_{ssl}, ACME server: {acme}]".format( 15 | prefix=env.prefix, 16 | aversion=env.get_httpd_version(), 17 | ssl=env.ssl_module, 18 | acme=env.acme_server, 19 | ) 20 | 21 | 22 | @pytest.fixture(scope="package") 23 | def env(pytestconfig) -> MDTestEnv: 24 | level = logging.INFO 25 | console = logging.StreamHandler() 26 | console.setLevel(level) 27 | console.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) 28 | logging.getLogger('').addHandler(console) 29 | logging.getLogger('').setLevel(level=level) 30 | env = MDTestEnv(pytestconfig=pytestconfig) 31 | env.setup_httpd() 32 | env.apache_access_log_clear() 33 | env.httpd_error_log.clear_log() 34 | yield env 35 | env.apache_stop() 36 | 37 | 38 | @pytest.fixture(autouse=True, scope="package") 39 | def _md_package_scope(env): 40 | env.httpd_error_log.add_ignored_lognos([ 41 | "AH10085" # There are no SSL certificates configured and no other module contributed any 42 | ]) 43 | 44 | 45 | @pytest.fixture(scope="package") 46 | def acme(env): 47 | acme_server = None 48 | if env.acme_server == 'pebble': 49 | acme_server = MDPebbleRunner(env, configs={ 50 | 'default': os.path.join(env.gen_dir, 'pebble/pebble.json'), 51 | 'eab': os.path.join(env.gen_dir, 'pebble/pebble-eab.json'), 52 | }) 53 | elif env.acme_server == 'boulder': 54 | acme_server = MDBoulderRunner(env) 55 | yield acme_server 56 | if acme_server is not None: 57 | acme_server.stop() 58 | 59 | -------------------------------------------------------------------------------- /test/modules/md/data/sectigo-demo-root.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDejCCAmKgAwIBAgIQfaNPgBFNflrkSu7M6HnZoTANBgkqhkiG9w0BAQsFADBX 3 | MQswCQYDVQQGEwJDQTELMAkGA1UECBMCT04xDzANBgNVBAcTBk90dGF3YTEQMA4G 4 | A1UEChMHT3B0b3ZpYTEYMBYGA1UEAxMPT3B0b3ZpYSBSb290IENBMB4XDTIxMDgy 5 | NzEyMzUxNloXDTMwMTIzMTIzNTk1OVowVzELMAkGA1UEBhMCQ0ExCzAJBgNVBAgT 6 | Ak9OMQ8wDQYDVQQHEwZPdHRhd2ExEDAOBgNVBAoTB09wdG92aWExGDAWBgNVBAMT 7 | D09wdG92aWEgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB 8 | ANN0W9COV5gViQS2XOxJw5+aiuZNk5byuVW+IJ06BpPArz5rlQdv78ze014Ogjwa 9 | KoGeSTdV2tmZpBajLVGlYHuMQUwzRwd8Z9qHk1HU8wPRo3seMesaYcE2y5KEpgcv 10 | 9xFaszEXsFU67ZFBSshsp01/A088aBk4S+quQImwSy1Z5Gm8f+U7GvF9DPCqKhUc 11 | EUXSb8HEY70EAkeEMKsW0sNkcH9hXxVKBdmoB0j2tMnTdogoxP9JLiJxYOk2Y25v 12 | Ci9GzIYzyeiDa/K2nX4Ow2bpCPhR8Y4D8kxQcAC9fD5ZbFgcRitfL2+/OZhyq23J 13 | 4PSAwOmvPWMwXxUuM7V0APUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgGGMA8GA1Ud 14 | EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMf2hZ3tBqCfzXRBZtAv/2lZQlaRMA0GCSqG 15 | SIb3DQEBCwUAA4IBAQAsNsj8myBHijCE2F4db9ggiwFE1v+aHmsWcou7dhWJK7J7 16 | SkzE78eCXZNt/Ftc7xzHBHu+IExGztXoLwqygGc7tWcHw9/8cgkHpAVa8kCiAVjn 17 | v+LrDwNiNm3JzMT+Q2HXEOZyFadqNxMuvBZI98h8ZzZDfYIgmyP/bYFTtt3FdFtb 18 | DQXq89rlrfBAIN+I0S8KidKXIgWBKC6oiBUrNg/1yHRjuS1+VL3j2jCo6XOco0BH 19 | lw4CR0f19XVblhXhP1yuzyp232z6SV96Gi2ZGZwMGu0cPGW3bj/o9MFW34LbJAXM 20 | Pg1c48PpmQbkA4YOhkI3ocDpF/3nkkeLdzb+p3x1 21 | -----END CERTIFICATE----- 22 | 23 | -------------------------------------------------------------------------------- /test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.json: -------------------------------------------------------------------------------- 1 | { 2 | "disabled": false, 3 | "url": "http://localhost:4000/acme/reg/494", 4 | "ca-url": "http://localhost:4000/directory", 5 | "id": "ACME-localhost-0000" 6 | } -------------------------------------------------------------------------------- /test/modules/md/data/store_migrate/1.0/sample1/accounts/ACME-localhost-0000/account.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIJnzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQI0s8pf5rIPTECAggA 3 | MB0GCWCGSAFlAwQBKgQQ2u9SobgmVMhhZxYkXf9kpwSCCVD04Xywr0m+b5f+2aE5 4 | qjGr8y6xlf4NC/+QL6mBCw+9tlsgt7Z9bBt7PR1eMUQ0Bz5a9veBT2JwqGFU8XLv 5 | Anfd4a8ciKRx4kdP7JL08rkKAqPxuwkzMin3TeOJwsoghyvt8zFrXrWEcHyhHd4L 6 | HAoA3ccCxDHH7ydORd7rhEQUOkcjbaJkZi6pzvv+C7kgSTMKYBaI1mlNzX5Oxm6I 7 | ziwmDcOtRgKb17z26zOYWjzbKHGopPlFe9/l32JxTr5UuCihR4NoPGiK08280OWQ 8 | HIwRxQ900AKyJZM1q3RkH4r0xtiik0lX0isx+UIiNEefA4Za/kXLCM7hHVCGwF1z 9 | eE8oX2yNcsX/sw7aVLhRyVDzrT8C5T7+s+K0eV/hfyYXXAZ0z0H+l3f3TRbMlLuq 10 | 1FQnOmEtQy0CbfPGNlzbiK3glp2fc2ZHubTkprMoRTkEKWNiXD0Suhnsll9eV3d2 11 | cHZgsCQyD3LRz+Xj2v6P+fDOcu7IuM7om9GEjNQB1e7dzo6HOSTG2mIsQo6VByJw 12 | syoK1zzC70Jhj/G6aFALTh4dMceoBDyHZzOfiVwC3dGX1QEnNvGD7Za/woMNIx8S 13 | hiqjntDhlXPXCRX/Z/Zvg///6+Ip9FqkCVk74DRWjH9iUzdP7/E1GCyAH2BSdsdc 14 | PnK15p79Ff5TMV91IQmnVV37s57VqXIez2RtuLd530iUk4RtkJ1/PphybHd+JW/n 15 | avMj8gsuWB7RqaBsmbjLmSudSl0DNgy0IJKZs11UifrZmSkaUJH+JJ1W2hLHR980 16 | X75IujUmZasWYkVqq0nvdy8JConCaLd3TT8r8DcO73vZqjFnN+EEHENaEg7F7ig8 17 | xkp0wk4F3u1BEnkwd34aLonZ9DtSK3miDRqlWXqQGESMaQLYQvHUn9q4X57Tyz4T 18 | 9ZVPeLJiuHwCGq6z2BJhgkAlGs7Eqra0pMpjVnRdylTQzx0Q2vLQbrZasyBpReeM 19 | zGdadxRR84PyhAGDGdLKR8VCVFhWX32ZBfqJQOjpyAT30Wu11ZDvEPASuTL4GdcD 20 | o5seucpUZdgzrivvjUhYLkRd0WOjgJyuvtWdillpSiweeGfDAnZvUZUFLd4EMmwH 21 | W+IUr7yIsjNuGZU3NW0pW/L9d9GuwgljP61WKhS6B7hRmx22YU3z2Y7islXiey3m 22 | kZ37mAqdK4EIQca2j9GmBQk7oUz+boYdm4vtk7tJI07LEDI79U95B8x1MpzjuIbj 23 | zlYmH1yw8UefsFrOfjJ4BpkDjVux+J2DmSqCFb5XBcjwWsYiY17niW6Qfrypd6vq 24 | bew1HgbBhdBNQoL1P8uS1fNNwoHmhJc6PNHFFxU3NP91yqB8Igj3khqk9+/VBcCt 25 | 8xRc/1jR5mfAgvaCWyQgIZAsCgTLnvEXy91MG/DKR0ZdOLZJNas+1W9fjhcFvP6S 26 | nNmeMMrIAxaI85RVvnLqPEZhsb9AOlyaf6tKFJiCteyQlie6MOQTKSp4jjSOVW+w 27 | q/WtSZup9zXo8Ek+TnLhD0IJhpIbfR5is5iZaVY7lbcg4pc3Csh/SiMUJ4TJgiPS 28 | /End7LPoRIabRnw4PBtJRNCwf3ilsWUmi95HU3wLAmLpI1AtnbfQi+zva4UJdOTV 29 | HJxNN84ZGuey1gG7qZb3U6WpwzQDKvqTm5jK32nIS/LuNv1qpv0FdAmvulV9wBar 30 | M19CcD5kOlTvNZcf6B4Fkrr+x+Anji/kUV4slIvUbAaU9P4lMO0ORCTg1es7QvI7 31 | v0KRYYSULrO+G2CNYL7fN8Vf5tRpBZ3H1o6u3plw/P86MTQPOskppjK1VKsBBmL2 32 | isdeumWjLpFVr1vWxTm68f88f+iau3BRUkCDQXFEVTN7YuOhpexb6Js0T220HYTS 33 | 9hmeVUnNlXii1BpnxLhBx/0O3heVOLc/C7b7vASg5PljieUQmpuyeJSUAJm1vKrI 34 | p2G/46MgBl+3/NkzLRGepzAH2IGAhhtXEk/zePdRptbVr29+vGDX6IzEWqZ5UYHG 35 | P5JYzaojrmLd0BNYwEbCrRBRHyM4jYFkRERs/kwCh5/Kle/eZpb+bjvIsAs0xcOC 36 | /uRF8RfHW1h8M8Bm9tR+rUX8CTxaIF3IY+N5qSPstNt8xGYLv7uvd+KoK0xVHAm+ 37 | FAreqql7koa5D0ncLjTpQGnHiLBKsYmJWC4+TKC+a5m0eKmRgO/r5o+7mmoB9qCZ 38 | bI9GB9HoYeVW/QVWfmoH0W6rbQCmK/VcSB1dGwvz9rKU1DXHhXvGU2k1IAfPX11t 39 | RfwUmmLtrM9tjOWdBh74N4G8UvTk5FGygzJ+Eclm/ABeAChIFU7mLJFejOue/bKq 40 | CRAQul45+CskNyVyZWZvWTFT0UMN290b4E4sjUKoLbFZiA1Y/aU+ruG9iwPJ3yVS 41 | s09VqogNwKBLWYW5TclUzgf71AQTlnZpTudkqwr36ogIAXXaQpE1f6/HLQz3k1PA 42 | WmTaxoM//X00WvTq2UxxSmKf7mNPEg9UZ9m4ZTKe35a//ONxXVjBjtK23yN5MuHY 43 | YrgWF84xlLRPY3Um2ukCsRGb7yZRhlPmOBeYQvRod7BqEA0UmIR+ctnBWDwzSZw7 44 | JWuR+AZdjIfM+Ilh15fokpLI5IFnTAqvTYDoF0185kqYPkjtI2STAWpALA9XJp70 45 | aF/rbdbSrRPFI1+izTIvQjffYftro7EOfCFv62XZm6tj5RLHalfgTcWoUWw81ylL 46 | DOZZaKsv4bOW7HCM47pitFojwzNf9OaHd5VTaSPWts49siF/qCxcG8bwu51picbc 47 | 96H1h3/npNhxDUA5qKzkBK9Bs7panzXt2kNJxPzHEiCjVVGq7t/ei4TZGoSw806D 48 | kNPFhztVoM1k2m7F7lu1EYOwJH/yXKJUgJYIycIoQyRMX7h0jb76U0oOHrdkw3A2 49 | 9Helksl8kqz10td2PZyoj3K/EWu+33cFKgLtC9JrDATR3Lhdo2N3BQQAotW2+Tht 50 | HqHj/UzUoIWcEkzCZeJhRn9WRRbbLeWKwdXBxGl0ZESpJJ2+Ml6QkMkdZSUzDURD 51 | kxYl04U9JXk6vC2hT6780OBLnLivBqIaSUJ72DSkOFnifFoP/OeglWFVkJHWQjQP 52 | aGMcPD/xLLYhdRQlJND9K12FXtsazW2K/V+861y4rJOt6zJGSZwPrQBkLf7QBNAC 53 | DWiLOvp6tLT58pX8TSlplbITcQ== 54 | -----END ENCRYPTED PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /test/modules/md/data/store_migrate/1.0/sample1/archive/7007-1502285564.org.1/md.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "7007-1502285564.org", 3 | "domains": [ 4 | "7007-1502285564.org" 5 | ], 6 | "contacts": [ 7 | "mailto:admin@7007-1502285564.org" 8 | ], 9 | "transitive": 0, 10 | "ca": { 11 | "proto": "ACME", 12 | "url": "http://localhost:4000/directory", 13 | "agreement": "http://boulder:4000/terms/v1" 14 | }, 15 | "state": 1, 16 | "renew-mode": 2, 17 | "renew-window": 1209600 18 | } 19 | -------------------------------------------------------------------------------- /test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFkDCCBHigAwIBAgITAP8PGcftT0j60OOjL+Er/XuHrzANBgkqhkiG9w0BAQsF 3 | ADAfMR0wGwYDVQQDDBRoMnBweSBoMmNrZXIgZmFrZSBDQTAeFw0xNzA4MDkxMjMz 4 | MDBaFw0xNzExMDcxMjMzMDBaME0xHDAaBgNVBAMTEzcwMDctMTUwMjI4NTU2NC5v 5 | cmcxLTArBgNVBAUTJGZmMGYxOWM3ZWQ0ZjQ4ZmFkMGUzYTMyZmUxMmJmZDdiODdh 6 | ZjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMHuhVxT9Jpc6EpNAhrq 7 | RqzDJ4tWSG9BtguKZzh3sbY92EE5rqym7wpdb5DG5gwew4iD1R+YizY+99+00qlB 8 | 3kNBUVsJCBnew0apmhPq4jjF8v8t3Qqq0ISn2Sdv5bt5mB9NWeO83h3zT1LW0rTm 9 | 847nwxUuGxlIjLXxsibUvPunMfyGJUshflN5V9/Q3YQBOCnDWy5s4FKN2N34cHFE 10 | IgJo5ToBKZLp9eUaLm03mlfhTFc3/h0AtWwMZ5P2tRRB9EiijqI9nkrVzqyi1QTN 11 | Hn/XfgDgKRCyMp6i5kcK3hCXo4GjOIU0KA91ttf3IeKhXHKzC7ybc4hdJH2rWzoN 12 | srYq6tNZ+cOaa1E/H+v+OMSeIRaRrpM56c3nUssIzbneMIXuLHuOluaaL4baCjYp 13 | Pdc80bUlps06XcnVHysAbsfbtWAtUdzj2l4flVySruGoaqVDudl1GqYoYa+0oReM 14 | Zqd09Q+pCQvDNE+jiVq3An+JA4msux9EMMz7jkAwnl8iiWy0GMuQPsL5gp3TEXGY 15 | Cp1wQlzpmxZSdUZ+J6f4UkFOS/Zn6gS6nSxN8nj3XKbRYRbebPQMwRGYGttCyeZO 16 | dHiUY/3gQBUdpcMBJhAa5GFoabK0J5XPmK2E1P9cGQo7DbNn+Skojnz2WuUtCuyo 17 | m9la14Ruca9V8NmjBsu+4mXvAgMBAAGjggGVMIIBkTAOBgNVHQ8BAf8EBAMCBaAw 18 | HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYD 19 | VR0OBBYEFH426IYgY0KXUe9cLMZ3d8tipsDkMB8GA1UdIwQYMBaAFPt4TxL5YBWD 20 | LJ8XfzQZsy426kGJMGYGCCsGAQUFBwEBBFowWDAiBggrBgEFBQcwAYYWaHR0cDov 21 | LzEyNy4wLjAuMTo0MDAyLzAyBggrBgEFBQcwAoYmaHR0cDovLzEyNy4wLjAuMTo0 22 | MDAwL2FjbWUvaXNzdWVyLWNlcnQwHgYDVR0RBBcwFYITNzAwNy0xNTAyMjg1NTY0 23 | Lm9yZzAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8vZXhhbXBsZS5jb20vY3JsMGEG 24 | A1UdIARaMFgwCAYGZ4EMAQIBMEwGAyoDBDBFMCIGCCsGAQUFBwIBFhZodHRwOi8v 25 | ZXhhbXBsZS5jb20vY3BzMB8GCCsGAQUFBwICMBMMEURvIFdoYXQgVGhvdSBXaWx0 26 | MA0GCSqGSIb3DQEBCwUAA4IBAQBfqLXSJZ5Izs2I44cXWrAto631aTylValp0Fiy 27 | Zz1dj00FS6XN5DGtfIyq7Ymd3MMiOZCLkTOMMb7BrJAvcgeJteKwdk3ffXEDyKH0 28 | 1ttXK7l46trEyGOB+f9PMMKxVMyhDhGKyb6ro4Y5WTK/w4862soqKcP1SjHvk65u 29 | lIkFws1fWYYzqPLKLij2ILm+4NjdGIl8qPQWP2PtbOaDTFspJBz6hvLmqRgmjVVv 30 | cENwBUML4LCkVY3TUqoBHXDhpocTZlVeAVRVsroosboQJlY5nIKz6cOjilILn4cT 31 | hgEKa5IRwK5lUveCoeQtYUyLoyp5ncbota+UxNxCnkl/0veK 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEijCCA3KgAwIBAgICEk0wDQYJKoZIhvcNAQELBQAwKzEpMCcGA1UEAwwgY2Fj 3 | a2xpbmcgY3J5cHRvZ3JhcGhlciBmYWtlIFJPT1QwHhcNMTUxMDIxMjAxMTUyWhcN 4 | MjAxMDE5MjAxMTUyWjAfMR0wGwYDVQQDExRoYXBweSBoYWNrZXIgZmFrZSBDQTCC 5 | ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIKR3maBcUSsncXYzQT13D5 6 | Nr+Z3mLxMMh3TUdt6sACmqbJ0btRlgXfMtNLM2OU1I6a3Ju+tIZSdn2v21JBwvxU 7 | zpZQ4zy2cimIiMQDZCQHJwzC9GZn8HaW091iz9H0Go3A7WDXwYNmsdLNRi00o14U 8 | joaVqaPsYrZWvRKaIRqaU0hHmS0AWwQSvN/93iMIXuyiwywmkwKbWnnxCQ/gsctK 9 | FUtcNrwEx9Wgj6KlhwDTyI1QWSBbxVYNyUgPFzKxrSmwMO0yNff7ho+QT9x5+Y/7 10 | XE59S4Mc4ZXxcXKew/gSlN9U5mvT+D2BhDtkCupdfsZNCQWp27A+b/DmrFI9NqsC 11 | AwEAAaOCAcIwggG+MBIGA1UdEwEB/wQIMAYBAf8CAQAwQwYDVR0eBDwwOqE4MAaC 12 | BC5taWwwCocIAAAAAAAAAAAwIocgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 13 | AAAAAAAwDgYDVR0PAQH/BAQDAgGGMH8GCCsGAQUFBwEBBHMwcTAyBggrBgEFBQcw 14 | AYYmaHR0cDovL2lzcmcudHJ1c3RpZC5vY3NwLmlkZW50cnVzdC5jb20wOwYIKwYB 15 | BQUHMAKGL2h0dHA6Ly9hcHBzLmlkZW50cnVzdC5jb20vcm9vdHMvZHN0cm9vdGNh 16 | eDMucDdjMB8GA1UdIwQYMBaAFOmkP+6epeby1dd5YDyTpi4kjpeqMFQGA1UdIARN 17 | MEswCAYGZ4EMAQIBMD8GCysGAQQBgt8TAQEBMDAwLgYIKwYBBQUHAgEWImh0dHA6 18 | Ly9jcHMucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcwPAYDVR0fBDUwMzAxoC+gLYYr 19 | aHR0cDovL2NybC5pZGVudHJ1c3QuY29tL0RTVFJPT1RDQVgzQ1JMLmNybDAdBgNV 20 | HQ4EFgQU+3hPEvlgFYMsnxd/NBmzLjbqQYkwDQYJKoZIhvcNAQELBQADggEBAA0Y 21 | AeLXOklx4hhCikUUl+BdnFfn1g0W5AiQLVNIOL6PnqXu0wjnhNyhqdwnfhYMnoy4 22 | idRh4lB6pz8Gf9pnlLd/DnWSV3gS+/I/mAl1dCkKby6H2V790e6IHmIK2KYm3jm+ 23 | U++FIdGpBdsQTSdmiX/rAyuxMDM0adMkNBwTfQmZQCz6nGHw1QcSPZMvZpsC8Skv 24 | ekzxsjF1otOrMUPNPQvtTWrVx8GlR2qfx/4xbQa1v2frNvFBCmO59goz+jnWvfTt 25 | j2NjwDZ7vlMBsPm16dbKYC840uvRoZjxqsdc3ChCZjqimFqlNG/xoPA8+dTicZzC 26 | XE9ijPIcvW6y1aa3bGw= 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/md.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "7007-1502285564.org", 3 | "domains": [ 4 | "7007-1502285564.org" 5 | ], 6 | "contacts": [ 7 | "mailto:admin@7007-1502285564.org" 8 | ], 9 | "transitive": 0, 10 | "ca": { 11 | "account": "ACME-localhost-0000", 12 | "proto": "ACME", 13 | "url": "http://localhost:4000/directory", 14 | "agreement": "http://boulder:4000/terms/v1" 15 | }, 16 | "cert": { 17 | "url": "http://localhost:4000/acme/cert/ff0f19c7ed4f48fad0e3a32fe12bfd7b87af", 18 | "expires": "Tue, 07 Nov 2017 12:33:00 GMT" 19 | }, 20 | "state": 2, 21 | "renew-mode": 2, 22 | "renew-window": 1209600 23 | } 24 | -------------------------------------------------------------------------------- /test/modules/md/data/store_migrate/1.0/sample1/domains/7007-1502285564.org/pkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDB7oVcU/SaXOhK 3 | TQIa6kaswyeLVkhvQbYLimc4d7G2PdhBOa6spu8KXW+QxuYMHsOIg9UfmIs2Pvff 4 | tNKpQd5DQVFbCQgZ3sNGqZoT6uI4xfL/Ld0KqtCEp9knb+W7eZgfTVnjvN4d809S 5 | 1tK05vOO58MVLhsZSIy18bIm1Lz7pzH8hiVLIX5TeVff0N2EATgpw1subOBSjdjd 6 | +HBxRCICaOU6ASmS6fXlGi5tN5pX4UxXN/4dALVsDGeT9rUUQfRIoo6iPZ5K1c6s 7 | otUEzR5/134A4CkQsjKeouZHCt4Ql6OBoziFNCgPdbbX9yHioVxyswu8m3OIXSR9 8 | q1s6DbK2KurTWfnDmmtRPx/r/jjEniEWka6TOenN51LLCM253jCF7ix7jpbmmi+G 9 | 2go2KT3XPNG1JabNOl3J1R8rAG7H27VgLVHc49peH5Vckq7hqGqlQ7nZdRqmKGGv 10 | tKEXjGandPUPqQkLwzRPo4latwJ/iQOJrLsfRDDM+45AMJ5fIolstBjLkD7C+YKd 11 | 0xFxmAqdcEJc6ZsWUnVGfien+FJBTkv2Z+oEup0sTfJ491ym0WEW3mz0DMERmBrb 12 | QsnmTnR4lGP94EAVHaXDASYQGuRhaGmytCeVz5ithNT/XBkKOw2zZ/kpKI589lrl 13 | LQrsqJvZWteEbnGvVfDZowbLvuJl7wIDAQABAoICAQCVSZob0v1O/wpKeDGQqpwx 14 | TiHY31jvXHRZOffvviRtl/ora84NVoxZPEgv+Q0Kc3wuUN31bqZr4dlKupYYeX4x 15 | 48xO+grkb1l/wfu8LWpsLeW7joDEP245UESYWUlOInJ6Vj9GUxPhlnWP3ZNicw83 16 | CS5h1ZZCxlibjy2HOukoCDMwo8t9pJDsjVKaFt0PSykC7UH54RJmOo+hgCh+6OYN 17 | WNZs6owobjY+YQMwTEdiMytjUNUrWmpOfNYXTyliKMt2RrzqI+kAzspElyzIf2Zl 18 | H2v+HJFAKw1QlTITqkf8Gd9iYlWWJOpZzFIuui25mmHiYfY9AKXVaW4313tomzbg 19 | L9Muc0pCmR8ge/hsC+C2QkVhHRFThakd5zU8rOEeXClzLKg1tjSVwcyNllXwd3Uy 20 | gQRtDqAWcWhXj2pqPzLc4v/wobjPE+xEpAbvDBvEof1fMy1PBeyKq7T4mIxswuWF 21 | takm9/Bt15K2TNBc7qNQV2x+MCS0Bi2Hd1yjLbIHllBDQR2ZsHRw1D38ckbL7ATE 22 | yDwnzI2gxlYYV7K/iQG9XkM54Ra5tNOFYv9GiCw+JPrLcQ5qmGsCCu6lfktMC8pN 23 | 7VQRbHt60ZKaunE1muwWDmyYzP106qUXMw6nIVMyqX0ywTEPAgtRgWcucLWR33DD 24 | k1OBcq2tOceaZjA5Pbi4sQKCAQEA+MbI4HEbROlsPeQ7VMOoAHjJPWuhDNXqnz4Q 25 | c4z3X+W61TAWZINRENYDZd3c7D7wOWb9VBA+o62xrzYviul9qhTAjZ8dRfxagJpH 26 | OxNY348HNj+IxONj3RXr/7tfOXtzcjiFwzn85oPLRM56XfjYZ5lUgQBSEauXOue5 27 | +bpNBvrYZLPm7i5BM8RpBElH2wtCizLAE9BrKYUqTYWyl76miPfpeSVMv2JOpUwp 28 | josVrAWAOoQHeIrCLmSF43oqmtzJ9Aq1r/VeOQB/3TT4E0RhWhDWOg3zNuA20w+E 29 | VuKyl4J/XLo6T86Zc/PM4+vb8zPztjZHQVJj58Iq7N4/y5cBfQKCAQEAx5AP10sw 30 | C4kCwU/yXORhimMPlRldKx2h+8Ha/0whTkehXaJ0synCV0ZLh7jSgfe81Zx5/3RK 31 | KKRWVx7+wmQiOqfSIBJN4xWdpVDS7yndk/FW8sYqT1v2sgr2t1u41bQAY3qzezsK 32 | elNsjbRsUCVvVu9HZ5zH7Pvmf0Ma8P2t8EioQWJ2ptgF6imTXIrQORJPBqDEzp6W 33 | EjiHC9kuZ2E+uPGl+6oQcxRUjtFkxnI9LgpOQCjNNIhW6cEhJxV3z8YIUnUyd7vd 34 | i0eEfhKF+DXzrqbtve63iGGU7TFMiiNF59hPxKHkPvHnUlXNZjJ8om9M579i/9fm 35 | OHYWaWFuzb6g2wKCAQAIZ37FxkxriY80kA9JD8sPKQVzY71vF5Lzij84CB0bSkGD 36 | jjpTbvRAI1q+CD68ZGvtJIOOYXYcRXPpPWVhxf2Oz2Cp6CQvBxVvnsalQkQQWV6f 37 | AIp4TE5FW8Y7P3M6F+eQhkROkhjvGKi3TFpp7kwxQ8bNDNu46RkUzltECn0rrTG+ 38 | RS2aAkoFm68IjAk3Zyv6U96VTMcyAeOp9shPxAsQOX/TreTn2kRZ5TbKL/ytcQoh 39 | 7+/orJdexdqYErp5vNe9vNbieOGT/2ZSbMWssPSw/DygfXQn+G8htjZ8UPBDmg7/ 40 | bPMnWw1oE2ZqlL87ehfTogXKOSRS4gZdNizljdZpAoIBADxSfZdUcOdruNt6MQaH 41 | Ojy8iN9G1XTM9kPFa080UfT5jfthuejWPJpo8zfJVEhY/EmNjQr8udXjJv4armNQ 42 | JVCZndh37/cud4KbFceZXhL0JpYn9G4cnEthKQZvwUVHrb5kPpCHXjlvsiZ7XSo0 43 | xpz+oxTcvUoTMq9RN3mVFNjG/aUWAEuajN8lRhf5FcvKjvyv6A2UvkQvthKMyYwS 44 | RwVcdhHGbEZ85Lpu7QlXSsr57oFSVAUHGU57RGwt/xNdBvL13hV3QhZxvcjmDHzk 45 | wg4PA1ogKHYfGQdBmaM/2kekiSgkz3t/X67xpK65oBbxkcuTfHddaYezmj6sZvPm 46 | JXUCggEBAO37OxP7B66FQghuBkfui8sPymY2oSFQIb3IRO5A17/wp9yW1f9X4Bu4 47 | dh7ln+6IEURZyldAZcVRSHbjrL8VWXtS86eDttnKD7L46BbqAytckc/pebA/5bu0 48 | tjsM8ulayPGuJzEl/g1F1bU1eduXkmq/O7636S0Q1KCVHldn9qNgkowfjpzANHNs 49 | ksSwxMIY8n4U2kckMmfCj2B6UrnqQ6Bs7IaijQJ5u/mGYke+gKEGQ99esx2Ts1Vl 50 | w8WDaDUOwHEywuFyqtGJzizX8BazIzwmSCh8hpedDtFVVnfjszLnf3Y+FOrb9XlM 51 | Wc8hH7giOwSubI2D2mauspM5CZlez7A= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /test/modules/md/data/store_migrate/1.0/sample1/httpd.json: -------------------------------------------------------------------------------- 1 | { 2 | "proto": { 3 | "http": true, 4 | "https": true 5 | } 6 | } -------------------------------------------------------------------------------- /test/modules/md/data/store_migrate/1.0/sample1/md_store.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.6.1-git", 3 | "store": { 4 | "version": 1.0 5 | }, 6 | "key": "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2" 7 | } -------------------------------------------------------------------------------- /test/modules/md/data/test_920/002.pubcert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFYDCCBEigAwIBAgISAwOcRk1FTt55/NLK6Fn2aPJpMA0GCSqGSIb3DQEBCwUA 3 | MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD 4 | ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTA1MzExNjA2MzVaFw0x 5 | OTA4MjkxNjA2MzVaMBYxFDASBgNVBAMTC2Vpc3Npbmcub3JnMIIBIjANBgkqhkiG 6 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9d5xZdknImIPfmiUaiiRhHLx4bvazWRTgA2+ 7 | etRNKr42MRjkuLbAhvxGjhw4El0GJlbngKTfiSK0Vq0idW/ehUr++czRSDrRVfqq 8 | qcI/F4NXLIbIZfmR7/vG0IP8Xc8D9VyQCX0uDapCvw+A/U46p0VOZz4bIB/bl0BW 9 | /mqBvVhBU9owskUcPjwwI/tK6My933CUVKXuFpPZ4V7zoY0/8Xa6JmWC2q1+7XmE 10 | h51hPnU35dYH1bA7WblX8rVxnEPCyCOgABVLKb6NhWfTCEqy+yzr32KsoSR1xqe4 11 | T2EeTcoamwF2yhz2zRC4glX0LM4inJ1/ZOQ+nKbFZTOPVWEnLQIDAQABo4ICcjCC 12 | Am4wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD 13 | AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTfO7pZGPLsa0NuPZMG4NGlr1TaWjAf 14 | BgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEw 15 | LgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcw 16 | LwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv 17 | MCcGA1UdEQQgMB6CC2Vpc3Npbmcub3Jngg93d3cuZWlzc2luZy5vcmcwTAYDVR0g 18 | BEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0 19 | cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEFBgorBgEEAdZ5AgQCBIH2BIHzAPEA 20 | dwB0ftqDMa0zEJEhnM4lT0Jwwr/9XkIgCMY3NXnmEHvMVgAAAWsO24QlAAAEAwBI 21 | MEYCIQD8yd2uHl2DNgvnBkSiA8vsK5pOv204NixI9F89LWERwgIhAPMLLiZkFG2h 22 | DTpEwF50BbZ+laYH8VP03Teq5csk2lX0AHYAKTxRllTIOWW6qlD8WAfUt2+/WHop 23 | ctykwwz05UVH9HgAAAFrDtuEFgAABAMARzBFAiEA3bYpKSNigSe0HuDyH/kerTW2 24 | 55ugvODp6d+vNbNmgZoCIGTd4cio769BTKfLJTqNbjc9sKK9T7XkHUO4JgQdY6Nq 25 | MA0GCSqGSIb3DQEBCwUAA4IBAQBeatZxh8leVmeFE/IYTKKqHyZqTccJKdugXIOr 26 | uIF6sLup/8Fv/2N0wZc+edkj+NCyWhxxkZULyW6xhlL7rtzcwLYbQBSxKvT4Utur 27 | 01a5bwhM62MdMjzkFgCCa5nRKPQ7bc684RrUFNi94d0KSb5ArFv8wovqPW7jbmFp 28 | X50dYKCE+wohFPHcsQapnV0lXK4+5qJZSZkp/pHANdndLCvFfzRHhV4nqRA12G2T 29 | VVWjdHN6ShL2uykJVAnSBhu/XD4mh79Yq9TQtS1DHfP3HcKstLqR0nrwBFaB6087 30 | jXfIpJ46yObq001qHeUMhT+B3WI2YPp/hY7u8A9+hCmDyyq8 31 | -----END CERTIFICATE----- 32 | -----BEGIN CERTIFICATE----- 33 | MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ 34 | MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT 35 | DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow 36 | SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT 37 | GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC 38 | AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF 39 | q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 40 | SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 41 | Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA 42 | a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj 43 | /PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T 44 | AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG 45 | CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv 46 | bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k 47 | c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw 48 | VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC 49 | ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz 50 | MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu 51 | Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF 52 | AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo 53 | uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ 54 | wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu 55 | X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG 56 | PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 57 | KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== 58 | -----END CERTIFICATE----- 59 | -------------------------------------------------------------------------------- /test/modules/md/data/test_conf_validate/test_014.conf: -------------------------------------------------------------------------------- 1 | # global server name as managed domain name 2 | 3 | MDomain resistance.fritz.box www.example2.org 4 | 5 | 6 | ServerName www.example2.org 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/modules/md/data/test_drive/test1.example.org.conf: -------------------------------------------------------------------------------- 1 | # A setup that required manual driving, e.g. invoking a2md outside apache 2 | # 3 | MDRenewMode manual 4 | 5 | MDomain test1.not-forbidden.org www.test1.not-forbidden.org mail.test1.not-forbidden.org 6 | 7 | -------------------------------------------------------------------------------- /test/modules/md/data/test_roundtrip/temp.conf: -------------------------------------------------------------------------------- 1 | MDDriveMode manual 2 | MDCertificateAuthority http://localhost:4000/directory 3 | MDCertificateProtocol ACME 4 | MDCertificateAgreement http://boulder:4000/terms/v1 5 | 6 | ServerAdmin mailto:admin@test102-1499953506.org 7 | 8 | ManagedDomain test102-1499953506.org test-a.test102-1499953506.org test-b.test102-1499953506.org 9 | 10 | 11 | ServerName test-a.test102-1499953506.org 12 | DocumentRoot htdocs/a 13 | 14 | SSLEngine on 15 | SSLCertificateFile /Users/sei/projects/mod_md/test/gen/apache/md/domains/test102-1499953506.org/cert.pem 16 | SSLCertificateKeyFile /Users/sei/projects/mod_md/test/gen/apache/md/domains/test102-1499953506.org/pkey.pem 17 | 18 | 19 | 20 | ServerName test-b.test102-1499953506.org 21 | DocumentRoot htdocs/b 22 | 23 | SSLEngine on 24 | SSLCertificateFile /Users/sei/projects/mod_md/test/gen/apache/md/domains/test102-1499953506.org/cert.pem 25 | SSLCertificateKeyFile /Users/sei/projects/mod_md/test/gen/apache/md/domains/test102-1499953506.org/pkey.pem 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/modules/md/dns01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | import sys 5 | 6 | curl = "curl" 7 | challtestsrv = "localhost:8055" 8 | 9 | 10 | def run(args): 11 | sys.stderr.write(f"run: {' '.join(args)}\n") 12 | p = subprocess.Popen(args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 13 | output, errput = p.communicate(None) 14 | rv = p.wait() 15 | if rv != 0: 16 | sys.stderr.write(errput.decode()) 17 | sys.stdout.write(output.decode()) 18 | return rv 19 | 20 | 21 | def teardown(domain): 22 | rv = run([curl, '-s', '-d', f'{{"host":"_acme-challenge.{domain}"}}', 23 | f'{challtestsrv}/clear-txt']) 24 | if rv == 0: 25 | rv = run([curl, '-s', '-d', f'{{"host":"{domain}"}}', 26 | f'{challtestsrv}/set-txt']) 27 | return rv 28 | 29 | 30 | def setup(domain, challenge): 31 | teardown(domain) 32 | rv = run([curl, '-s', '-d', f'{{"host":"{domain}", "addresses":["127.0.0.1"]}}', 33 | f'{challtestsrv}/set-txt']) 34 | if rv == 0: 35 | rv = run([curl, '-s', '-d', f'{{"host":"_acme-challenge.{domain}.", "value":"{challenge}"}}', 36 | f'{challtestsrv}/set-txt']) 37 | return rv 38 | 39 | 40 | def main(argv): 41 | if len(argv) > 1: 42 | if argv[1] == 'setup': 43 | if len(argv) != 4: 44 | sys.stderr.write("wrong number of arguments: dns01.py setup \n") 45 | sys.exit(2) 46 | rv = setup(argv[2], argv[3]) 47 | elif argv[1] == 'teardown': 48 | if len(argv) != 3: 49 | sys.stderr.write("wrong number of arguments: dns01.py teardown \n") 50 | sys.exit(1) 51 | rv = teardown(argv[2]) 52 | else: 53 | sys.stderr.write(f"unknown option {argv[1]}\n") 54 | rv = 2 55 | else: 56 | sys.stderr.write("dns01.py wrong number of arguments\n") 57 | rv = 2 58 | sys.exit(rv) 59 | 60 | 61 | if __name__ == "__main__": 62 | main(sys.argv) 63 | -------------------------------------------------------------------------------- /test/modules/md/dns01_v2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | import sys 5 | 6 | curl = "curl" 7 | challtestsrv = "localhost:8055" 8 | 9 | 10 | def run(args): 11 | sys.stderr.write(f"run: {' '.join(args)}\n") 12 | p = subprocess.Popen(args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 13 | output, errput = p.communicate(None) 14 | rv = p.wait() 15 | if rv != 0: 16 | sys.stderr.write(errput.decode()) 17 | sys.stdout.write(output.decode()) 18 | return rv 19 | 20 | 21 | def teardown(domain): 22 | rv = run([curl, '-s', '-d', f'{{"host":"_acme-challenge.{domain}"}}', 23 | f'{challtestsrv}/clear-txt']) 24 | if rv == 0: 25 | rv = run([curl, '-s', '-d', f'{{"host":"{domain}"}}', 26 | f'{challtestsrv}/set-txt']) 27 | return rv 28 | 29 | 30 | def setup(domain, challenge): 31 | teardown(domain) 32 | rv = run([curl, '-s', '-d', f'{{"host":"{domain}", "addresses":["127.0.0.1"]}}', 33 | f'{challtestsrv}/set-txt']) 34 | if rv == 0: 35 | rv = run([curl, '-s', '-d', f'{{"host":"_acme-challenge.{domain}.", "value":"{challenge}"}}', 36 | f'{challtestsrv}/set-txt']) 37 | return rv 38 | 39 | 40 | def main(argv): 41 | if len(argv) > 1: 42 | if argv[1] == 'setup': 43 | if len(argv) != 4: 44 | sys.stderr.write("wrong number of arguments: dns01.py setup \n") 45 | sys.exit(2) 46 | rv = setup(argv[2], argv[3]) 47 | elif argv[1] == 'teardown': 48 | if len(argv) != 4: 49 | sys.stderr.write("wrong number of arguments: dns01.py teardown \n") 50 | sys.exit(1) 51 | rv = teardown(argv[2]) 52 | else: 53 | sys.stderr.write(f"unknown option {argv[1]}\n") 54 | rv = 2 55 | else: 56 | sys.stderr.write("dns01.py wrong number of arguments\n") 57 | rv = 2 58 | sys.exit(rv) 59 | 60 | 61 | if __name__ == "__main__": 62 | main(sys.argv) 63 | -------------------------------------------------------------------------------- /test/modules/md/http_challenge_foobar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import re 4 | import sys 5 | 6 | 7 | def main(argv): 8 | if len(argv) < 4: 9 | sys.stderr.write(f"{argv[0]} without too few arguments") 10 | sys.exit(7) 11 | store_dir = argv[1] 12 | event = argv[2] 13 | mdomain = argv[3] 14 | m = re.match(r'(\S+):(\S+):(\S+)', event) 15 | if m and 'challenge-setup' == m.group(1) and 'http-01' == m.group(2): 16 | dns_name = m.group(3) 17 | challenge_file = f"{store_dir}/challenges/{dns_name}/acme-http-01.txt" 18 | if not os.path.isfile(challenge_file): 19 | sys.stderr.write(f"{argv[0]} does not exist: {challenge_file}") 20 | sys.exit(8) 21 | with open(challenge_file, 'w') as fd: 22 | fd.write('this_is_an_invalidated_http-01_challenge') 23 | sys.exit(0) 24 | 25 | 26 | if __name__ == "__main__": 27 | main(sys.argv) 28 | -------------------------------------------------------------------------------- /test/modules/md/md_conf.py: -------------------------------------------------------------------------------- 1 | from .md_env import MDTestEnv 2 | from pyhttpd.conf import HttpdConf 3 | 4 | 5 | class MDConf(HttpdConf): 6 | 7 | def __init__(self, env: MDTestEnv, text=None, std_ports=True, 8 | local_ca=True, std_vhosts=True, proxy=False, 9 | admin=None): 10 | super().__init__(env=env) 11 | 12 | if admin is None: 13 | admin = f"admin@{env.http_tld}" 14 | if len(admin.strip()): 15 | self.add_admin(admin) 16 | self.add([ 17 | "MDRetryDelay 1s", # speed up testing a little 18 | ]) 19 | if local_ca: 20 | self.add([ 21 | f"MDCertificateAuthority {env.acme_url}", 22 | f"MDCertificateAgreement accepted", 23 | f"MDCACertificateFile {env.server_dir}/acme-ca.pem", 24 | "", 25 | ]) 26 | if std_ports: 27 | self.add(f"MDPortMap 80:{env.http_port} 443:{env.https_port}") 28 | if env.ssl_module == "mod_tls": 29 | self.add(f"TLSListen {env.https_port}") 30 | self.add([ 31 | "", 32 | " SetHandler server-status", 33 | "", 34 | "", 35 | " SetHandler md-status", 36 | "", 37 | ]) 38 | if std_vhosts: 39 | self.add_vhost_test1() 40 | if proxy: 41 | self.add([ 42 | f"Listen {self.env.proxy_port}", 43 | f"", 44 | " ProxyRequests On", 45 | " ProxyVia On", 46 | " # be totally open", 47 | " AllowCONNECT 0-56535", 48 | " ", 49 | " # No require or other restrictions, this is just a test server", 50 | " ", 51 | "", 52 | ]) 53 | if text is not None: 54 | self.add(text) 55 | 56 | def add_drive_mode(self, mode): 57 | self.add("MDRenewMode \"%s\"\n" % mode) 58 | 59 | def add_renew_window(self, window): 60 | self.add("MDRenewWindow %s\n" % window) 61 | 62 | def add_private_key(self, key_type, key_params): 63 | self.add("MDPrivateKeys %s %s\n" % (key_type, " ".join(map(lambda p: str(p), key_params)))) 64 | 65 | def add_admin(self, email): 66 | self.add(f"ServerAdmin mailto:{email}") 67 | 68 | def add_md(self, domains): 69 | dlist = " ".join(domains) # without quotes 70 | self.add(f"MDomain {dlist}\n") 71 | 72 | def start_md(self, domains): 73 | dlist = " ".join([f"\"{d}\"" for d in domains]) # with quotes, #257 74 | self.add(f"\n") 75 | 76 | def end_md(self): 77 | self.add("\n") 78 | 79 | def start_md2(self, domains): 80 | self.add("\n" % " ".join(domains)) 81 | 82 | def end_md2(self): 83 | self.add("\n") 84 | -------------------------------------------------------------------------------- /test/modules/md/message.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | 7 | def main(argv): 8 | if len(argv) > 2: 9 | cmd = argv[2] 10 | if 'renewing' != cmd: 11 | f1 = open(argv[1], 'a+') 12 | f1.write(f'{argv}\n') 13 | if 'MD_VERSION' in os.environ: 14 | f1.write(f'MD_VERSION={os.environ["MD_VERSION"]}\n') 15 | if 'MD_STORE' in os.environ: 16 | f1.write(f'MD_STORE={os.environ["MD_STORE"]}\n') 17 | f1.close() 18 | sys.stderr.write("done, all fine.\n") 19 | sys.exit(0) 20 | else: 21 | sys.stderr.write(f"{argv[0]} without arguments") 22 | sys.exit(7) 23 | 24 | 25 | if __name__ == "__main__": 26 | main(sys.argv) 27 | -------------------------------------------------------------------------------- /test/modules/md/msg_fail_on.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | 7 | def main(argv): 8 | if len(argv) > 3: 9 | log = argv[1] 10 | fail_on = argv[2] 11 | cmd = argv[3] 12 | domain = argv[4] 13 | if 'renewing' != cmd: 14 | f1 = open(log, 'a+') 15 | f1.write(f"{[argv[0], log, cmd, domain]}\n") 16 | f1.close() 17 | if cmd.startswith(fail_on): 18 | sys.stderr.write(f"failing on: {cmd}\n") 19 | sys.exit(1) 20 | sys.stderr.write("done, all fine.\n") 21 | sys.exit(0) 22 | else: 23 | sys.stderr.write("%s without arguments" % (argv[0])) 24 | sys.exit(7) 25 | 26 | 27 | if __name__ == "__main__": 28 | main(sys.argv) 29 | -------------------------------------------------------------------------------- /test/modules/md/notifail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | 6 | def main(argv): 7 | if len(argv) > 1: 8 | msg = argv[2] if len(argv) > 2 else None 9 | # fail on later messaging stages, not the initial 'renewing' one. 10 | # we have test_901_030 that check that later stages are not invoked 11 | # when misconfigurations are detected early. 12 | sys.exit(1 if msg != "renewing" else 0) 13 | 14 | 15 | if __name__ == "__main__": 16 | main(sys.argv) 17 | -------------------------------------------------------------------------------- /test/modules/md/notify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | 6 | def main(argv): 7 | if len(argv) > 2: 8 | with open(argv[1], 'a+') as f1: 9 | f1.write(f"{argv}\n") 10 | sys.stderr.write("done, all fine.\n") 11 | sys.exit(0) 12 | else: 13 | sys.stderr.write(f"{argv[0]} without arguments") 14 | sys.exit(7) 15 | 16 | 17 | if __name__ == "__main__": 18 | main(sys.argv) 19 | -------------------------------------------------------------------------------- /test/modules/md/pebble/pebble-eab.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "pebble": { 3 | "listenAddress": "0.0.0.0:14000", 4 | "managementListenAddress": "0.0.0.0:15000", 5 | "certificate": "${server_dir}/ca/localhost.rsa2048.cert.pem", 6 | "privateKey": "${server_dir}/ca/localhost.rsa2048.pkey.pem", 7 | "httpPort": ${http_port}, 8 | "tlsPort": ${https_port}, 9 | "ocspResponderURL": "", 10 | "externalAccountBindingRequired": true, 11 | "externalAccountMACKeys": { 12 | "kid-1": "zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W", 13 | "kid-2": "b10lLJs8l1GPIzsLP0s6pMt8O0XVGnfTaCeROxQM0BIt2XrJMDHJZBM5NuQmQJQH" 14 | }, 15 | "profiles": { 16 | "default": { 17 | "description": "The profile you know and love", 18 | "validityPeriod": 7776000 19 | }, 20 | "shortlived": { 21 | "description": "A short-lived cert profile, without actual enforcement", 22 | "validityPeriod": 518400 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/modules/md/pebble/pebble.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "pebble": { 3 | "listenAddress": "0.0.0.0:14000", 4 | "managementListenAddress": "0.0.0.0:15000", 5 | "certificate": "${server_dir}/ca/localhost.rsa2048.cert.pem", 6 | "privateKey": "${server_dir}/ca/localhost.rsa2048.pkey.pem", 7 | "httpPort": ${http_port}, 8 | "tlsPort": ${https_port}, 9 | "ocspResponderURL": "", 10 | "externalAccountBindingRequired": false, 11 | "domainBlocklist": ["blocked-domain.example"], 12 | "retryAfter": { 13 | "authz": 3, 14 | "order": 5 15 | }, 16 | "profiles": { 17 | "default": { 18 | "description": "The profile you know and love", 19 | "validityPeriod": 7776000 20 | }, 21 | "shortlived": { 22 | "description": "A short-lived cert profile, without actual enforcement", 23 | "validityPeriod": 518400 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/modules/md/test_010_store_migrate.py: -------------------------------------------------------------------------------- 1 | # test mod_md acme terms-of-service handling 2 | 3 | import os 4 | import pytest 5 | 6 | from .md_conf import MDConf 7 | from .md_env import MDTestEnv 8 | 9 | 10 | @pytest.mark.skipif(condition=not MDTestEnv.has_a2md(), reason="no a2md available") 11 | class TestStoreMigrate: 12 | 13 | @pytest.fixture(autouse=True, scope='class') 14 | def _class_scope(self, env): 15 | MDConf(env).install() 16 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 17 | 18 | # install old store, start a2md list, check files afterwards 19 | def test_md_010_000(self, env): 20 | domain = "7007-1502285564.org" 21 | env.replace_store(os.path.join(env.test_dir, "../modules/md/data/store_migrate/1.0/sample1")) 22 | # 23 | # use 1.0 file name for private key 24 | fpkey_1_0 = os.path.join(env.store_dir, 'domains', domain, 'pkey.pem') 25 | fpkey_1_1 = os.path.join(env.store_dir, 'domains', domain, 'privkey.pem') 26 | cert_1_0 = os.path.join(env.store_dir, 'domains', domain, 'cert.pem') 27 | cert_1_1 = os.path.join(env.store_dir, 'domains', domain, 'pubcert.pem') 28 | chain_1_0 = os.path.join(env.store_dir, 'domains', domain, 'chain.pem') 29 | # 30 | assert os.path.exists(fpkey_1_0) 31 | assert os.path.exists(cert_1_0) 32 | assert os.path.exists(chain_1_0) 33 | assert not os.path.exists(fpkey_1_1) 34 | assert not os.path.exists(cert_1_1) 35 | # 36 | md = env.a2md(["-vvv", "list", domain]).json['output'][0] 37 | assert domain == md["name"] 38 | # 39 | assert not os.path.exists(fpkey_1_0) 40 | assert os.path.exists(cert_1_0) 41 | assert os.path.exists(chain_1_0) 42 | assert os.path.exists(fpkey_1_1) 43 | assert os.path.exists(cert_1_1) 44 | -------------------------------------------------------------------------------- /test/modules/md/test_741_setup_errors.py: -------------------------------------------------------------------------------- 1 | # test ACME error responses and their processing 2 | import os 3 | 4 | import pytest 5 | 6 | from .md_conf import MDConf 7 | from .md_env import MDTestEnv 8 | 9 | 10 | @pytest.mark.skipif(condition=not MDTestEnv.has_acme_server(), 11 | reason="no ACME test server configured") 12 | class TestSetupErrors: 13 | 14 | @pytest.fixture(autouse=True, scope='class') 15 | def _class_scope(self, env, acme): 16 | env.APACHE_CONF_SRC = "data/test_auto" 17 | acme.start(config='default') 18 | env.check_acme() 19 | env.clear_store() 20 | MDConf(env).install() 21 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 22 | 23 | @pytest.fixture(autouse=True, scope='function') 24 | def _method_scope(self, env, request): 25 | env.clear_store() 26 | self.mcmd = os.path.join(env.test_dir, "../modules/md/http_challenge_foobar.py") 27 | self.test_domain = env.get_request_domain(request) 28 | 29 | def test_md_741_001(self, env): 30 | # setup an MD with a MDMessageCmd that make the http-01 challenge file invalid 31 | # before the ACME server is asked to retrieve it. This will result in 32 | # an "invalid" domain authorization. 33 | # The certificate sign-up will be attempted again after 4 seconds and 34 | # of course fail again. 35 | # Verify that the error counter for the staging job increments, so 36 | # that our retry logic goes into proper delayed backoff. 37 | domain = self.test_domain 38 | domains = [domain] 39 | conf = MDConf(env) 40 | conf.add("MDCAChallenges http-01") 41 | conf.add(f"MDMessageCmd {self.mcmd} {env.store_dir}") 42 | conf.add_md(domains) 43 | conf.add_vhost(domains) 44 | conf.install() 45 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 46 | md = env.await_error(domain, errors=2, timeout=10) 47 | assert md 48 | assert md['renewal']['errors'] > 0 49 | # 50 | env.httpd_error_log.ignore_recent( 51 | lognos = [ 52 | "AH10056" # CA considers answer to challenge invalid 53 | ], 54 | matches = [ 55 | r'.*The key authorization file from the server did not match this challenge.*', 56 | r'.*CA considers answer to challenge invalid.*' 57 | ] 58 | ) 59 | 60 | # mess up the produced staging area before reload 61 | def test_md_741_002(self, env): 62 | domain = self.test_domain 63 | domains = [domain] 64 | conf = MDConf(env) 65 | conf.add_md(domains) 66 | conf.add_vhost(domains) 67 | conf.install() 68 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 69 | env.check_md(domains) 70 | assert env.await_completion([domain], restart=False) 71 | staged_md_path = env.store_staged_file(domain, 'md.json') 72 | with open(staged_md_path, 'w') as fd: 73 | fd.write('garbage\n') 74 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 75 | assert env.await_completion([domain]) 76 | env.check_md_complete(domain) 77 | env.httpd_error_log.ignore_recent( 78 | lognos = [ 79 | "AH10069" # failed to load JSON file 80 | ], 81 | matches = [ 82 | r'.*failed to load JSON file.*', 83 | ] 84 | ) 85 | -------------------------------------------------------------------------------- /test/modules/md/test_800_must_staple.py: -------------------------------------------------------------------------------- 1 | # test mod_md must-staple support 2 | import pytest 3 | 4 | from .md_conf import MDConf 5 | from .md_cert_util import MDCertUtil 6 | from .md_env import MDTestEnv 7 | 8 | 9 | @pytest.mark.skipif(condition=not MDTestEnv.has_acme_server(), 10 | reason="no ACME test server configured") 11 | class TestMustStaple: 12 | domain = None 13 | 14 | @pytest.fixture(autouse=True, scope='class') 15 | def _class_scope(self, env, acme): 16 | acme.start(config='default') 17 | env.check_acme() 18 | env.clear_store() 19 | MDConf(env).install() 20 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 21 | 22 | @pytest.fixture(autouse=True, scope='function') 23 | def _method_scope(self, env, request): 24 | self.domain = env.get_class_domain(self.__class__) 25 | 26 | def configure_httpd(self, env, domain, add_lines=""): 27 | conf = MDConf(env, admin="admin@" + domain) 28 | conf.add(add_lines) 29 | conf.add_md([domain]) 30 | conf.add_vhost(domain) 31 | conf.install() 32 | 33 | # MD with default, e.g. not staple 34 | def test_md_800_001(self, env): 35 | self.configure_httpd(env, self.domain) 36 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 37 | assert env.await_completion([self.domain]) 38 | env.check_md_complete(self.domain) 39 | cert1 = MDCertUtil(env.store_domain_file(self.domain, 'pubcert.pem')) 40 | assert not cert1.get_must_staple() 41 | 42 | # MD that should explicitly not staple 43 | def test_md_800_002(self, env): 44 | self.configure_httpd(env, self.domain, "MDMustStaple off") 45 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 46 | env.check_md_complete(self.domain) 47 | cert1 = MDCertUtil(env.store_domain_file(self.domain, 'pubcert.pem')) 48 | assert not cert1.get_must_staple() 49 | stat = env.get_ocsp_status(self.domain) 50 | assert 'ocsp' not in stat or stat['ocsp'] == "no response sent" 51 | 52 | # MD that must staple and toggle off again 53 | @pytest.mark.skipif(MDTestEnv.lacks_ocsp(), reason="no OCSP responder") 54 | def test_md_800_003(self, env): 55 | self.configure_httpd(env, self.domain, "MDMustStaple on") 56 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 57 | assert env.await_completion([self.domain]) 58 | env.check_md_complete(self.domain) 59 | cert1 = MDCertUtil(env.store_domain_file(self.domain, 'pubcert.pem')) 60 | assert cert1.get_must_staple() 61 | self.configure_httpd(env, self.domain, "MDMustStaple off") 62 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 63 | assert env.await_completion([self.domain]) 64 | env.check_md_complete(self.domain) 65 | cert1 = MDCertUtil(env.store_domain_file(self.domain, 'pubcert.pem')) 66 | assert not cert1.get_must_staple() 67 | 68 | # MD that must staple 69 | @pytest.mark.skipif(MDTestEnv.lacks_ocsp(), reason="no OCSP responder") 70 | @pytest.mark.skipif(MDTestEnv.get_ssl_module() != "mod_ssl", reason="only for mod_ssl") 71 | def test_md_800_004(self, env): 72 | # mod_ssl stapling is off, expect no stapling 73 | stat = env.get_ocsp_status(self.domain) 74 | assert stat['ocsp'] == "no response sent" 75 | # turn mod_ssl stapling on, expect an answer 76 | self.configure_httpd(env, self.domain, """ 77 | LogLevel ssl:trace2 78 | SSLUseStapling On 79 | SSLStaplingCache shmcb:stapling_cache(128000) 80 | """) 81 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 82 | stat = env.get_ocsp_status(self.domain) 83 | assert stat['ocsp'] == "successful (0x0)" 84 | assert stat['verify'] == "0 (ok)" 85 | -------------------------------------------------------------------------------- /test/modules/md/test_820_locks.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | from filelock import Timeout, FileLock 5 | 6 | from .md_cert_util import MDCertUtil 7 | from .md_conf import MDConf 8 | from .md_env import MDTestEnv 9 | 10 | 11 | @pytest.mark.skipif(condition=not MDTestEnv.has_acme_server(), 12 | reason="no ACME test server configured") 13 | class TestLocks: 14 | 15 | @pytest.fixture(autouse=True, scope='class') 16 | def _class_scope(self, env, acme): 17 | env.APACHE_CONF_SRC = "data/test_auto" 18 | acme.start(config='default') 19 | env.check_acme() 20 | env.clear_store() 21 | 22 | @pytest.fixture(autouse=True, scope='function') 23 | def _method_scope(self, env, request): 24 | env.clear_store() 25 | self.test_domain = env.get_request_domain(request) 26 | 27 | def configure_httpd(self, env, domains, add_lines=""): 28 | conf = MDConf(env) 29 | conf.add(add_lines) 30 | conf.add_md(domains) 31 | conf.add_vhost(domains) 32 | conf.install() 33 | 34 | # normal renewal with store locks activated 35 | def test_md_820_001(self, env): 36 | domain = self.test_domain 37 | self.configure_httpd(env, [domain], add_lines=[ 38 | "MDStoreLocks 1s" 39 | ]) 40 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 41 | assert env.await_completion([domain]) 42 | 43 | # renewal, with global lock held during restert 44 | @pytest.mark.skip("does not work in our CI") 45 | def test_md_820_002(self, env): 46 | domain = self.test_domain 47 | self.configure_httpd(env, [domain], add_lines=[ 48 | "MDStoreLocks 1s" 49 | ]) 50 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 51 | assert env.await_completion([domain]) 52 | # we have a cert now, add a dns name to force renewal 53 | certa = MDCertUtil(env.store_domain_file(domain, 'pubcert.pem')) 54 | self.configure_httpd(env, [domain, f"x.{domain}"], add_lines=[ 55 | "MDStoreLocks 1s" 56 | ]) 57 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 58 | # await new cert, but do not restart, keeps the cert in staging 59 | assert env.await_completion([domain], restart=False) 60 | # obtain global lock and restart 61 | lockfile = os.path.join(env.store_dir, "store.lock") 62 | with FileLock(lockfile): 63 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 64 | # lock should have prevented staging from being activated, 65 | # meaning we will have the same cert 66 | certb = MDCertUtil(env.store_domain_file(domain, 'pubcert.pem')) 67 | assert certa.same_serial_as(certb) 68 | # now restart without lock 69 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 70 | certc = MDCertUtil(env.store_domain_file(domain, 'pubcert.pem')) 71 | assert not certa.same_serial_as(certc) 72 | 73 | 74 | -------------------------------------------------------------------------------- /test/modules/md/test_910_cleanups.py: -------------------------------------------------------------------------------- 1 | # test mod_md cleanups and sanitation 2 | 3 | import os 4 | 5 | import pytest 6 | 7 | from .md_conf import MDConf 8 | from .md_env import MDTestEnv 9 | 10 | 11 | @pytest.mark.skipif(condition=not MDTestEnv.has_acme_server(), 12 | reason="no ACME test server configured") 13 | class TestCleanups: 14 | 15 | @pytest.fixture(autouse=True, scope='class') 16 | def _class_scope(self, env, acme): 17 | env.APACHE_CONF_SRC = "data/test_auto" 18 | acme.start(config='default') 19 | env.check_acme() 20 | env.clear_store() 21 | MDConf(env).install() 22 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 23 | 24 | @pytest.fixture(autouse=True, scope='function') 25 | def _method_scope(self, env, request): 26 | env.clear_store() 27 | self.test_domain = env.get_request_domain(request) 28 | 29 | def teardown_method(self, method): 30 | print("teardown_method: %s" % method.__name__) 31 | 32 | def test_md_910_01(self, env): 33 | # generate a simple MD 34 | domain = self.test_domain 35 | domains = [domain] 36 | conf = MDConf(env) 37 | conf.add_drive_mode("manual") 38 | conf.add_md(domains) 39 | conf.add_vhost(domain) 40 | conf.install() 41 | 42 | # create valid/invalid challenges subdirs 43 | challenges_dir = env.store_challenges() 44 | dirs_before = ["aaa", "bbb", domain, "zzz"] 45 | for name in dirs_before: 46 | os.makedirs(os.path.join(challenges_dir, name)) 47 | 48 | assert env.apache_restart() == 0, f'{env.apachectl_stderr}' 49 | # the one we use is still there 50 | assert os.path.isdir(os.path.join(challenges_dir, domain)) 51 | # and the others are gone 52 | missing_after = ["aaa", "bbb", "zzz"] 53 | for name in missing_after: 54 | assert not os.path.exists(os.path.join(challenges_dir, name)) 55 | -------------------------------------------------------------------------------- /test/pyhttpd/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/__init__.py -------------------------------------------------------------------------------- /test/pyhttpd/conf/httpd.conf.template: -------------------------------------------------------------------------------- 1 | ServerName localhost 2 | ServerRoot "${server_dir}" 3 | 4 | # not in 2.4.x 5 | #DefaultRuntimeDir logs 6 | PidFile "${server_dir}/logs/httpd.pid" 7 | 8 | Include "conf/modules.conf" 9 | 10 | DocumentRoot "${server_dir}/htdocs" 11 | 12 | 13 | LogFormat "{ \"request\": \"%r\", \"status\": %>s, \"bytes_resp_B\": %B, \"bytes_tx_O\": %O, \"bytes_rx_I\": %I, \"bytes_rx_tx_S\": %S, \"time_taken\": %D }" combined 14 | LogFormat "%h %l %u %t \"%r\" %>s %b" common 15 | CustomLog "logs/access_log" combined 16 | 17 | 18 | 19 | TypesConfig "${gen_dir}/apache/conf/mime.types" 20 | 21 | Listen ${http_port} 22 | Listen ${https_port} 23 | 24 | 25 | # provide some default 26 | SSLSessionCache "shmcb:ssl_gcache_data(32000)" 27 | 28 | 29 | # Insert our test specific configuration before the first vhost, 30 | # so that its vhosts can be the default one. This is relevant in 31 | # certain behaviours, such as protocol selection during SSL ALPN 32 | # negotiation. 33 | # 34 | Include "conf/test.conf" 35 | 36 | RequestReadTimeout header=10 body=10 37 | 38 | 39 | AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css 40 | 41 | 42 | AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/css 43 | 44 | 45 | 46 | ServerName ${http_tld} 47 | ServerAlias www.${http_tld} 48 | 49 | SSLEngine off 50 | 51 | DocumentRoot "${server_dir}/htdocs" 52 | 53 | 54 | 55 | Options Indexes FollowSymLinks 56 | AllowOverride None 57 | Require all granted 58 | 59 | AddHandler cgi-script .py 60 | AddHandler cgi-script .cgi 61 | Options +ExecCGI 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /test/pyhttpd/conf/stop.conf.template: -------------------------------------------------------------------------------- 1 | # a config safe to use for stopping the server 2 | # this allows us to stop the server even when+ 3 | # the config in the file is borked (as test cases may try to do that) 4 | # 5 | ServerName localhost 6 | ServerRoot "${server_dir}" 7 | 8 | # not in 2.4.x 9 | #DefaultRuntimeDir logs 10 | PidFile "${server_dir}/logs/httpd.pid" 11 | 12 | Include "conf/modules.conf" 13 | 14 | DocumentRoot "${server_dir}/htdocs" 15 | 16 | 17 | LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %k" combined 18 | LogFormat "%h %l %u %t \"%r\" %>s %b" common 19 | CustomLog "logs/access_log" combined 20 | 21 | 22 | 23 | TypesConfig "${gen_dir}/apache/conf/mime.types" 24 | 25 | Listen ${http_port} 26 | Listen ${https_port} 27 | 28 | 29 | # provide some default 30 | SSLSessionCache "shmcb:ssl_gcache_data(32000)" 31 | 32 | 33 | 34 | ServerName ${http_tld} 35 | ServerAlias www.${http_tld} 36 | 37 | SSLEngine off 38 | 39 | DocumentRoot "${server_dir}/htdocs" 40 | 41 | 42 | 43 | Options Indexes FollowSymLinks 44 | AllowOverride None 45 | Require all granted 46 | 47 | AddHandler cgi-script .py 48 | AddHandler cgi-script .cgi 49 | Options +ExecCGI 50 | 51 | -------------------------------------------------------------------------------- /test/pyhttpd/conf/test.conf: -------------------------------------------------------------------------------- 1 | # empty placeholder for test specific configurations 2 | -------------------------------------------------------------------------------- /test/pyhttpd/config.ini.in: -------------------------------------------------------------------------------- 1 | [global] 2 | curl_bin = curl 3 | nghttp = nghttp 4 | h2load = h2load 5 | 6 | prefix = @prefix@ 7 | exec_prefix = @exec_prefix@ 8 | bindir = @bindir@ 9 | sbindir = @sbindir@ 10 | libdir = @libdir@ 11 | libexecdir = @libexecdir@ 12 | 13 | apr_bindir = @APR_BINDIR@ 14 | apxs = @bindir@/apxs 15 | httpd = @HTTPD@ 16 | 17 | [httpd] 18 | version = @HTTPD_VERSION@ 19 | name = @progname@ 20 | dso_modules = @DSO_MODULES@ 21 | mpm_modules = @MPM_MODULES@ 22 | 23 | [test] 24 | src_dir = @abs_top_srcdir@ 25 | gen_dir = @abs_srcdir@/../gen 26 | http_port = 5002 27 | https_port = 5001 28 | proxy_port = 5003 29 | http_port2 = 5004 30 | ws_port = 5100 31 | http_tld = tests.httpd.apache.org 32 | test_dir = @abs_srcdir@ 33 | test_src_dir = @abs_srcdir@ 34 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/alive.json: -------------------------------------------------------------------------------- 1 | { 2 | "host" : "generic", 3 | "alive" : true 4 | } 5 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/cgi/echo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, cgi, os 3 | 4 | status = '200 Ok' 5 | 6 | content = '' 7 | for line in sys.stdin: 8 | content += line 9 | 10 | # Just echo what we get 11 | print("Status: 200") 12 | print(f"Request-Length: {len(content)}") 13 | print("Content-Type: application/data\n") 14 | sys.stdout.write(content) 15 | 16 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/cgi/echohd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import cgi, os 3 | import cgitb; cgitb.enable() 4 | 5 | status = '200 Ok' 6 | 7 | form = cgi.FieldStorage() 8 | name = form.getvalue('name') 9 | 10 | if name: 11 | print("Status: 200") 12 | print("""\ 13 | Content-Type: text/plain\n""") 14 | print("""%s: %s""" % (name, os.environ['HTTP_'+name])) 15 | else: 16 | print("Status: 400 Parameter Missing") 17 | print("""\ 18 | Content-Type: text/html\n 19 | 20 |

No name was specified

21 | """) 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/cgi/env.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import cgi, os 3 | import cgitb; cgitb.enable() 4 | 5 | status = '200 Ok' 6 | 7 | try: 8 | form = cgi.FieldStorage() 9 | input = form['name'] 10 | 11 | # Test if the file was uploaded 12 | if input.value is not None: 13 | val = os.environ[input.value] if input.value in os.environ else "" 14 | print("Status: 200") 15 | print("""\ 16 | Content-Type: text/plain\n""") 17 | print("{0}={1}".format(input.value, val)) 18 | 19 | else: 20 | print("Status: 400 Parameter Missing") 21 | print("""\ 22 | Content-Type: text/html\n 23 | 24 |

No name was specified: %s

25 | """ % (count.value)) 26 | 27 | except KeyError: 28 | print("Status: 200 Ok") 29 | print("""\ 30 | Content-Type: text/html\n 31 | 32 | Echo
33 | 34 |
35 | """) 36 | pass 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/cgi/files/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/cgi/files/empty.txt -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/cgi/hecho.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import cgi, os 3 | import cgitb; cgitb.enable() 4 | 5 | status = '200 Ok' 6 | 7 | try: 8 | form = cgi.FieldStorage() 9 | 10 | # A nested FieldStorage instance holds the file 11 | name = form['name'].value 12 | value = '' 13 | 14 | try: 15 | value = form['value'].value 16 | except KeyError: 17 | value = os.environ.get("HTTP_"+name, "unset") 18 | 19 | # Test if a value was given 20 | if name: 21 | print("Status: 200") 22 | print("%s: %s" % (name, value,)) 23 | print ("""\ 24 | Content-Type: text/plain\n""") 25 | 26 | else: 27 | print("Status: 400 Parameter Missing") 28 | print("""\ 29 | Content-Type: text/html\n 30 | 31 |

No name and value was specified: %s %s

32 | """ % (name, value)) 33 | 34 | except KeyError: 35 | print("Status: 200 Ok") 36 | print("""\ 37 | Content-Type: text/html\n 38 | 39 | Echo
40 | 41 | 42 |
43 | """) 44 | pass 45 | 46 | 47 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/cgi/hello.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | 5 | print("Content-Type: application/json") 6 | print() 7 | print("{") 8 | print(" \"https\" : \"%s\"," % (os.getenv('HTTPS', ''))) 9 | print(" \"host\" : \"%s\"," % (os.getenv('SERVER_NAME', ''))) 10 | print(" \"protocol\" : \"%s\"," % (os.getenv('SERVER_PROTOCOL', ''))) 11 | print(" \"ssl_protocol\" : \"%s\"," % (os.getenv('SSL_PROTOCOL', ''))) 12 | print(" \"h2\" : \"%s\"," % (os.getenv('HTTP2', ''))) 13 | print(" \"h2push\" : \"%s\"" % (os.getenv('H2PUSH', ''))) 14 | print("}") 15 | 16 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/cgi/mnot164.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import cgi 4 | import cgitb; cgitb.enable() 5 | import os 6 | import sys 7 | 8 | try: 9 | form = cgi.FieldStorage() 10 | count = form['count'].value 11 | text = form['text'].value 12 | except KeyError: 13 | text="a" 14 | count=77784 15 | 16 | 17 | print("Status: 200 OK") 18 | print("Content-Type: text/html") 19 | print() 20 | sys.stdout.write(text*int(count)) 21 | 22 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/cgi/necho.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import cgi, os 3 | import time 4 | import cgitb; cgitb.enable() 5 | 6 | status = '200 Ok' 7 | 8 | try: 9 | form = cgi.FieldStorage() 10 | count = form['count'] 11 | text = form['text'] 12 | 13 | waitsec = float(form['wait1'].value) if 'wait1' in form else 0.0 14 | if waitsec > 0: 15 | time.sleep(waitsec) 16 | 17 | if int(count.value): 18 | print("Status: 200") 19 | print("""\ 20 | Content-Type: text/plain\n""") 21 | 22 | waitsec = float(form['wait2'].value) if 'wait2' in form else 0.0 23 | if waitsec > 0: 24 | time.sleep(waitsec) 25 | 26 | i = 0; 27 | for i in range(0, int(count.value)): 28 | print("%s" % (text.value)) 29 | 30 | waitsec = float(form['wait3'].value) if 'wait3' in form else 0.0 31 | if waitsec > 0: 32 | time.sleep(waitsec) 33 | 34 | else: 35 | print("Status: 400 Parameter Missing") 36 | print("""\ 37 | Content-Type: text/html\n 38 | 39 |

No count was specified: %s

40 | """ % (count.value)) 41 | 42 | except KeyError: 43 | print("Status: 200 Ok") 44 | print("""\ 45 | Content-Type: text/html\n 46 | 47 | Echo
48 | 49 | 50 |
51 | """) 52 | pass 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/cgi/upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import cgi, os 3 | import cgitb 4 | cgitb.enable() 5 | 6 | status = '200 Ok' 7 | 8 | try: # Windows needs stdio set for binary mode. 9 | import msvcrt 10 | msvcrt.setmode (0, os.O_BINARY) # stdin = 0 11 | msvcrt.setmode (1, os.O_BINARY) # stdout = 1 12 | except ImportError: 13 | pass 14 | 15 | form = cgi.FieldStorage() 16 | 17 | # Test if the file was uploaded 18 | if 'file' in form: 19 | fileitem = form['file'] 20 | # strip leading path from file name to avoid directory traversal attacks 21 | fn = os.path.basename(fileitem.filename) 22 | f = open(('%s/files/%s' % (os.environ["DOCUMENT_ROOT"], fn)), 'wb'); 23 | f.write(fileitem.file.read()) 24 | f.close() 25 | message = "The file %s was uploaded successfully" % (fn) 26 | print("Status: 201 Created") 27 | print("Content-Type: text/html") 28 | print("Location: %s://%s/files/%s" % (os.environ["REQUEST_SCHEME"], os.environ["HTTP_HOST"], fn)) 29 | print("") 30 | print("

%s

" % (message)) 31 | 32 | elif 'remove' in form: 33 | remove = form['remove'].value 34 | try: 35 | fn = os.path.basename(remove) 36 | os.remove('./files/' + fn) 37 | message = 'The file "' + fn + '" was removed successfully' 38 | except OSError as e: 39 | message = 'Error removing ' + fn + ': ' + e.strerror 40 | status = '404 File Not Found' 41 | print("Status: %s" % (status)) 42 | print(""" 43 | Content-Type: text/html 44 | 45 | 46 |

%s

47 | """ % (message)) 48 | 49 | else: 50 | message = '''\ 51 | Upload File
52 | 53 |
54 | ''' 55 | print("Status: %s" % (status)) 56 | print("""\ 57 | Content-Type: text/html 58 | 59 | 60 |

%s

61 | """ % (message)) 62 | 63 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/forbidden.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 403 - Forbidden 4 | 5 | 6 |

403 - Forbidden

7 |

8 | An example of an error document. 9 |

10 | 11 | 12 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | mod_h2 test site generic 4 | 5 | 6 |

mod_h2 test site generic

7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/noh2/alive.json: -------------------------------------------------------------------------------- 1 | { 2 | "host" : "noh2", 3 | "alive" : true 4 | } 5 | 6 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/noh2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | mod_h2 test site noh2 4 | 5 | 6 |

mod_h2 test site noh2

7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/001.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HTML/2.0 Test File: 001 5 | 6 | 7 |

HTML/2.0 Test File: 001

8 |

This file only contains a simple HTML structure with plain text.

9 | 10 | 11 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/002.jpg -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/003.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HTML/2.0 Test File: 003 5 | 6 | 7 |

HTML/2.0 Test File: 003

8 |

This is a text HTML file with a big image:

9 |

GSMA Logo

10 | 11 | 12 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/003/003_img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/003/003_img.jpg -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/004/gophertiles.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/004/gophertiles.jpg -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/006.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HTML/2.0 Test File: 006 5 | 6 | 7 | 8 | 9 |

HTML/2.0 Test File: 006

10 |
This page contains: 11 |
    12 |
  • HTML 13 |
  • CSS 14 |
  • JavaScript 15 |
16 |
17 |
18 | 21 |
22 | 23 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/006/006.css: -------------------------------------------------------------------------------- 1 | @CHARSET "ISO-8859-1"; 2 | body{ 3 | background:HoneyDew; 4 | } 5 | p{ 6 | color:#0000FF; 7 | text-align:left; 8 | } 9 | 10 | h1{ 11 | color:#FF0000; 12 | text-align:center; 13 | } 14 | 15 | .listTitle{ 16 | font-size:large; 17 | } 18 | 19 | .listElements{ 20 | color:#3366FF 21 | } -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/006/006.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript Functions File 3 | */ 4 | function returnDate() 5 | { 6 | var currentDate; 7 | currentDate=new Date(); 8 | var dateString=(currentDate.getMonth()+1)+'/'+currentDate.getDate()+'/'+currentDate.getFullYear(); 9 | return dateString; 10 | } 11 | 12 | function returnHour() 13 | { 14 | var currentDate; 15 | currentDate=new Date(); 16 | var hourString=currentDate.getHours()+':'+currentDate.getMinutes()+':'+currentDate.getSeconds(); 17 | return hourString; 18 | } 19 | 20 | function javaScriptMessage(){ 21 | return 'This section is generated under JavaScript:
'; 22 | } 23 | 24 | function mainJavascript(){ 25 | document.write(javaScriptMessage()) 26 | document.write('
    '); 27 | document.write('
  • Current date (dd/mm/yyyy): ' + returnDate()); 28 | document.write('
    '); 29 | document.write('
  • Current time (hh:mm:ss): '+returnHour()); 30 | document.write('
'); 31 | } -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/006/header.html: -------------------------------------------------------------------------------- 1 | My Header Title 2 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/007.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTML/2.0 Test File: 007 6 | 7 | 8 |

HTML/2.0 Test File: 007

9 |

This page is used to send data from the client to the server:

10 |
11 | 12 | Name:
13 | Age:
14 | Gender: Male 15 | Female
16 | 17 | 18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/007/007.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import cgi, sys 4 | import cgitb; cgitb.enable() 5 | 6 | print "Content-Type: text/html;charset=UTF-8" 7 | print 8 | 9 | print """\ 10 | 11 | HTML/2.0 Test File: 007 (received data) 12 |

HTML/2.0 Test File: 007

""" 13 | 14 | # alternative output: parsed form params <-> plain POST body 15 | parseContent = True # <-> False 16 | 17 | if parseContent: 18 | print '

Data processed:

    ' 19 | form = cgi.FieldStorage() 20 | for name in form: 21 | print '
  • ', name, ': ', form[name].value, '
  • ' 22 | print '
' 23 | else: 24 | print '

POST data output:

'
25 | 	data = sys.stdin.read()
26 | 	print data
27 | 	print '
' 28 | 29 | print '' -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/009.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import cgi, sys, time 4 | import cgitb; cgitb.enable() 5 | 6 | print "Content-Type: text/html;charset=UTF-8" 7 | print 8 | 9 | print """\ 10 | 11 | HTML/2.0 Test File: 009 (server time) 12 |

HTML/2.0 Test File: 009

13 |

60 seconds of server time, one by one.

""" 14 | 15 | for i in range(60): 16 | s = time.strftime("%Y-%m-%d %H:%M:%S") 17 | print "
", s, "
" 18 | sys.stdout.flush() 19 | time.sleep(1) 20 | 21 | print "

done.

" -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/alive.json: -------------------------------------------------------------------------------- 1 | { 2 | "host" : "test1", 3 | "alive" : true 4 | } 5 | 6 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/ant.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/apache.org-files/ant.jpg -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/asf_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/apache.org-files/asf_logo.png -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/async-ads.js.br: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/apache.org-files/async-ads.js.br -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/cse.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/apache.org-files/cse.js -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/css.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/apache.org-files/css.css -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/jsapi.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/apache.org-files/jsapi.js -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/min.css.br: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/apache.org-files/min.css.br -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/mrunit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/apache.org-files/mrunit.jpg -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/search_box_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/apache.org-files/search_box_icon.png -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/small-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/apache.org-files/small-logo.png -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top:60px; 3 | background-color: #fff; 4 | } 5 | 6 | h1, h2, h3, h4, h5, h6 { 7 | text-transform: uppercase; 8 | } 9 | 10 | .navbar-default .navbar-toggle { 11 | border: none; 12 | } 13 | 14 | .navbar-toggle { 15 | margin-top: 0px; 16 | margin-bottom: 0px; 17 | } 18 | 19 | section { 20 | padding-bottom: 25px; 21 | } 22 | 23 | ul.white > li > a, 24 | a.white { 25 | color: #ddd; 26 | } 27 | 28 | ul.white > li > a:hover, 29 | a.white:hover { 30 | color: #fff; 31 | } 32 | 33 | .white { 34 | color: #fff; 35 | } 36 | 37 | .bg-gray { 38 | background: #eee; 39 | } 40 | 41 | .letter-header { 42 | display: block; 43 | background-color: #f3f3f3; 44 | color: #303284; 45 | text-align: center; 46 | } 47 | 48 | .list-unstyled { 49 | list-style: none; 50 | } 51 | 52 | .no-top-margin{ 53 | margin-top: 0px; 54 | } 55 | 56 | .no-btm-margin { 57 | margin-bottom: 0px; 58 | } 59 | 60 | 61 | /* Custom Left Tabs */ 62 | 63 | .tabs-left > .nav-tabs { 64 | border-bottom: 0; 65 | } 66 | 67 | .tabs-left > .nav-tabs > li, 68 | .tabs-right > .nav-tabs > li { 69 | float: none; 70 | } 71 | 72 | .tabs-left > .nav-tabs > li > a, 73 | .tabs-right > .nav-tabs > li > a { 74 | min-width: 74px; 75 | margin-right: 0; 76 | margin-bottom: 3px; 77 | border: none; 78 | } 79 | 80 | .tabs-left > .nav-tabs > li > a:hover:after { 81 | content: ""; 82 | position: absolute; 83 | right: -24px; 84 | top: 50%; 85 | width: 0; 86 | height: 0; 87 | border-top: 25px solid transparent; 88 | border-bottom: 25px solid transparent; 89 | border-left: 25px solid #eee; 90 | margin-top: -25px; 91 | } 92 | 93 | .tabs-left > .nav-tabs > .active > a:after, 94 | .tabs-left > .nav-tabs > .active > a:hover:after { 95 | content: ""; 96 | position: absolute; 97 | right: -24px; 98 | top: 50%; 99 | width: 0; 100 | height: 0; 101 | border-top: 25px solid transparent; 102 | border-bottom: 25px solid transparent; 103 | border-left: 25px solid #303284; 104 | margin-top: -25px; 105 | } 106 | 107 | 108 | .tabs-left > .nav-tabs > li > a { 109 | margin-right: -1px; 110 | width: 90%; 111 | } 112 | 113 | .tabs-left > .nav-tabs > li > a:hover, 114 | .tabs-left > .nav-tabs > li > a:focus { 115 | border: none; 116 | } 117 | 118 | /* Media Adjustments */ 119 | @media (max-width: 992px){ 120 | .navbar-nav > li > a { 121 | padding-left: 5px; 122 | } 123 | } 124 | 125 | /* Prevent anchor links from displaying under the navbar */ 126 | :target:before { 127 | content: ""; 128 | display: block; 129 | height: 30px; 130 | margin-top: -30px; 131 | } 132 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/apache.org-files/synapse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test1/apache.org-files/synapse.jpg -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | mod_h2 test site 4 | 5 | 6 |

mod_h2 test site

7 |

8 |

served directly

9 | 20 |

mod_proxyied

21 | 32 |

mod_rewritten

33 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test2/006/006.css: -------------------------------------------------------------------------------- 1 | @CHARSET "ISO-8859-1"; 2 | body{ 3 | background:HoneyDew; 4 | } 5 | p{ 6 | color:#0000FF; 7 | text-align:left; 8 | } 9 | 10 | h1{ 11 | color:#FF0000; 12 | text-align:center; 13 | } 14 | 15 | .listTitle{ 16 | font-size:large; 17 | } 18 | 19 | .listElements{ 20 | color:#3366FF 21 | } -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test2/10%abnormal.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test2/10%abnormal.txt -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test2/alive.json: -------------------------------------------------------------------------------- 1 | { 2 | "host" : "test2", 3 | "alive" : true 4 | } 5 | -------------------------------------------------------------------------------- /test/pyhttpd/htdocs/test2/x%2f.test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icing/mod_md/9071b12a11c824f73fc54a29c81be885e9580745/test/pyhttpd/htdocs/test2/x%2f.test -------------------------------------------------------------------------------- /test/pyhttpd/mod_aptest/mod_aptest.c: -------------------------------------------------------------------------------- 1 | /* Licensed to the Apache Software Foundation (ASF) under one or more 2 | * contributor license agreements. See the NOTICE file distributed with 3 | * this work for additional information regarding copyright ownership. 4 | * The ASF licenses this file to You under the Apache License, Version 2.0 5 | * (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | static void aptest_hooks(apr_pool_t *pool); 29 | 30 | AP_DECLARE_MODULE(aptest) = { 31 | STANDARD20_MODULE_STUFF, 32 | NULL, /* func to create per dir config */ 33 | NULL, /* func to merge per dir config */ 34 | NULL, /* func to create per server config */ 35 | NULL, /* func to merge per server config */ 36 | NULL, /* command handlers */ 37 | aptest_hooks, 38 | #if defined(AP_MODULE_FLAG_NONE) 39 | AP_MODULE_FLAG_ALWAYS_MERGE 40 | #endif 41 | }; 42 | 43 | 44 | static int aptest_post_read_request(request_rec *r) 45 | { 46 | const char *test_name = apr_table_get(r->headers_in, "AP-Test-Name"); 47 | if (test_name) { 48 | ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "test[%s]: %s", 49 | test_name, r->the_request); 50 | } 51 | return DECLINED; 52 | } 53 | 54 | /* Install this module into the apache2 infrastructure. 55 | */ 56 | static void aptest_hooks(apr_pool_t *pool) 57 | { 58 | ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, 59 | "installing hooks and handlers"); 60 | 61 | /* test case monitoring */ 62 | ap_hook_post_read_request(aptest_post_read_request, NULL, 63 | NULL, APR_HOOK_MIDDLE); 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /test/pyhttpd/result.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import timedelta 3 | from typing import Optional, Dict, List 4 | 5 | 6 | class ExecResult: 7 | 8 | def __init__(self, args: List[str], exit_code: int, 9 | stdout: bytes, stderr: bytes = None, 10 | stdout_as_list: List[bytes] = None, 11 | duration: timedelta = None): 12 | self._args = args 13 | self._exit_code = exit_code 14 | self._stdout = stdout if stdout is not None else b'' 15 | self._stderr = stderr if stderr is not None else b'' 16 | self._duration = duration if duration is not None else timedelta() 17 | self._response = None 18 | self._results = {} 19 | self._assets = [] 20 | # noinspection PyBroadException 21 | try: 22 | if stdout_as_list is None: 23 | out = self._stdout.decode() 24 | else: 25 | out = "[" + ','.join(stdout_as_list) + "]" 26 | self._json_out = json.loads(out) 27 | except: 28 | self._json_out = None 29 | 30 | def __repr__(self): 31 | out = [ 32 | f"ExecResult[code={self.exit_code}, args={self._args}\n", 33 | "----stdout---------------------------------------\n", 34 | self._stdout.decode(), 35 | "----stderr---------------------------------------\n", 36 | self._stderr.decode() 37 | ] 38 | return ''.join(out) 39 | 40 | @property 41 | def exit_code(self) -> int: 42 | return self._exit_code 43 | 44 | @property 45 | def args(self) -> List[str]: 46 | return self._args 47 | 48 | @property 49 | def outraw(self) -> bytes: 50 | return self._stdout 51 | 52 | @property 53 | def stdout(self) -> str: 54 | return self._stdout.decode() 55 | 56 | @property 57 | def json(self) -> Optional[Dict]: 58 | """Output as JSON dictionary or None if not parseable.""" 59 | return self._json_out 60 | 61 | @property 62 | def stderr(self) -> str: 63 | return self._stderr.decode() 64 | 65 | @property 66 | def duration(self) -> timedelta: 67 | return self._duration 68 | 69 | @property 70 | def response(self) -> Optional[Dict]: 71 | return self._response 72 | 73 | @property 74 | def results(self) -> Dict: 75 | return self._results 76 | 77 | @property 78 | def assets(self) -> List: 79 | return self._assets 80 | 81 | def add_response(self, resp: Dict): 82 | if self._response: 83 | resp['previous'] = self._response 84 | self._response = resp 85 | 86 | def add_results(self, results: Dict): 87 | self._results.update(results) 88 | if 'response' in results: 89 | self.add_response(results['response']) 90 | 91 | def add_assets(self, assets: List): 92 | self._assets.extend(assets) 93 | -------------------------------------------------------------------------------- /test/requirements.txt: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2025 Stefan Eissing (https://dev-icing.de) 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | pytest 17 | cryptography 18 | filelock 19 | python-multipart 20 | psutil 21 | -------------------------------------------------------------------------------- /test/unit/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | #include "test_common.h" 16 | 17 | #include 18 | 19 | static Suite *main_test_suite(void) 20 | { 21 | Suite *suite = suite_create("main"); 22 | 23 | suite_add_tcase(suite, md_json_test_case()); 24 | suite_add_tcase(suite, md_util_test_case()); 25 | 26 | return suite; 27 | } 28 | 29 | int main(int argc, const char * const argv[]) 30 | { 31 | SRunner *runner; 32 | int failed; 33 | 34 | /* Initialize APR and create our test runner. */ 35 | apr_app_initialize(&argc, &argv, NULL); 36 | runner = srunner_create(main_test_suite()); 37 | 38 | /* Log TAP to stdout. */ 39 | srunner_set_tap(runner, "-"); 40 | 41 | /* Run the tests and collect failures. */ 42 | srunner_run_all(runner, CK_SILENT /* output only TAP */); 43 | failed = srunner_ntests_failed(runner); 44 | 45 | /* Clean up. */ 46 | srunner_free(runner); 47 | apr_terminate(); 48 | 49 | return failed ? 1 : 0; 50 | } 51 | -------------------------------------------------------------------------------- /test/unit/test_common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | /* 16 | * Common headers and declarations needed by most/all test source files. 17 | */ 18 | 19 | #include /* for pid_t on Windows, needed by Check */ 20 | #include 21 | 22 | /* 23 | * Compatibility ck_assert macros for Check < 0.11. 24 | */ 25 | #if (CHECK_MAJOR_VERSION == 0) && (CHECK_MINOR_VERSION < 11) 26 | # include /* fabs() */ 27 | # include /* memcmp() */ 28 | 29 | # define ck_assert_double_eq_tol(a, b, tol) \ 30 | ck_assert(fabs((a) - (b)) <= (tol)) 31 | # define ck_assert_mem_eq(a, b, len) \ 32 | ck_assert(!memcmp((a), (b), (len))) 33 | # define ck_assert_ptr_nonnull(p) \ 34 | ck_assert((p) != NULL) 35 | #endif 36 | 37 | /* 38 | * A list of Check test case declarations, usually one per source file. Add your 39 | * test case here when adding a new source file, then add it to the 40 | * main_test_suite() in main.c. 41 | */ 42 | 43 | TCase *md_json_test_case(void); 44 | TCase *md_util_test_case(void); 45 | -------------------------------------------------------------------------------- /test/unit/test_md_util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | #include 16 | 17 | #include "test_common.h" 18 | #include "md_util.h" 19 | 20 | /* 21 | * Helpers 22 | */ 23 | 24 | /* 25 | * Test Fixture -- runs once per test 26 | */ 27 | 28 | static apr_pool_t *g_pool; 29 | 30 | static void md_util_setup(void) 31 | { 32 | if (apr_pool_create(&g_pool, NULL) != APR_SUCCESS) { 33 | exit(1); 34 | } 35 | } 36 | 37 | static void md_util_teardown(void) 38 | { 39 | apr_pool_destroy(g_pool); 40 | } 41 | 42 | static void base64_roundtrip(const char *buf_in, size_t buf_len) 43 | { 44 | const char *buf64; 45 | md_data_t buffer; 46 | 47 | buffer.data = buf_in; 48 | buffer.len = buf_len; 49 | 50 | buf64 = md_util_base64url_encode(&buffer, g_pool); 51 | ck_assert(buf64); 52 | md_util_base64url_decode(&buffer, buf64, g_pool); 53 | 54 | ck_assert_int_eq(buf_len, buffer.len); 55 | ck_assert_mem_eq(buf_in, buffer.data, buffer.len); 56 | } 57 | 58 | /* 59 | * Tests 60 | */ 61 | START_TEST(base64_md_util_roundtrip) 62 | { 63 | base64_roundtrip("1", 1); 64 | base64_roundtrip("12", 2); 65 | base64_roundtrip("123", 3); 66 | base64_roundtrip("1234", 4); 67 | base64_roundtrip("12345", 5); 68 | base64_roundtrip("123456", 6); 69 | base64_roundtrip("1234567", 7); 70 | base64_roundtrip("12345678", 8); 71 | base64_roundtrip("123456789", 9); 72 | } 73 | END_TEST 74 | 75 | static void largetrip(int step) 76 | { 77 | char buffer[256]; 78 | int i, start; 79 | 80 | for (start = 0; start < 256; ++start) { 81 | for (i = 0; i < 256; ++i) { 82 | buffer[(start+(i*step)) % 256] = (char)i; 83 | } 84 | base64_roundtrip(buffer, 256); 85 | } 86 | } 87 | 88 | START_TEST(base64_md_util_largetrip) 89 | { 90 | largetrip(1); 91 | largetrip(3); 92 | largetrip(5); 93 | largetrip(17); 94 | largetrip(31); 95 | largetrip(53); 96 | largetrip(101); 97 | largetrip(167); 98 | largetrip(223); 99 | } 100 | END_TEST 101 | 102 | TCase *md_util_test_case(void) 103 | { 104 | TCase *testcase = tcase_create("md_util"); 105 | 106 | tcase_add_checked_fixture(testcase, md_util_setup, md_util_teardown); 107 | 108 | tcase_add_test(testcase, base64_md_util_roundtrip); 109 | tcase_add_test(testcase, base64_md_util_largetrip); 110 | 111 | return testcase; 112 | } 113 | --------------------------------------------------------------------------------